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

markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/11.0.x by this push:
     new ff635dfe36 Fully align order output with OpenSSL
ff635dfe36 is described below

commit ff635dfe364320ee9f05b72718204f53f53483cb
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Jun 12 17:05:23 2026 +0100

    Fully align order output with OpenSSL
---
 .../ciphers/OpenSSLCipherConfigurationParser.java  | 99 +++++++++++++++-------
 .../TestOpenSSLCipherConfigurationParser.java      | 27 +++---
 .../TestOpenSSLCipherConfigurationParserOnly.java  | 17 ++++
 webapps/docs/changelog.xml                         |  4 +
 4 files changed, 107 insertions(+), 40 deletions(-)

diff --git 
a/java/org/apache/tomcat/util/net/openssl/ciphers/OpenSSLCipherConfigurationParser.java
 
b/java/org/apache/tomcat/util/net/openssl/ciphers/OpenSSLCipherConfigurationParser.java
index 70f846e124..a48d52148d 100644
--- 
a/java/org/apache/tomcat/util/net/openssl/ciphers/OpenSSLCipherConfigurationParser.java
+++ 
b/java/org/apache/tomcat/util/net/openssl/ciphers/OpenSSLCipherConfigurationParser.java
@@ -526,16 +526,16 @@ public class OpenSSLCipherConfigurationParser {
     }
 
     static void moveToEnd(final LinkedHashSet<Cipher> ciphers, final 
Collection<Cipher> toBeMovedCiphers) {
-        List<Cipher> movedCiphers = new ArrayList<>(toBeMovedCiphers);
-        movedCiphers.retainAll(ciphers);
+        List<Cipher> movedCiphers = new ArrayList<>(ciphers);
+        movedCiphers.retainAll(toBeMovedCiphers);
         movedCiphers.forEach(ciphers::remove);
         ciphers.addAll(movedCiphers);
     }
 
     static void moveToStart(final LinkedHashSet<Cipher> ciphers, final 
Collection<Cipher> toBeMovedCiphers) {
-        List<Cipher> movedCiphers = new ArrayList<>(toBeMovedCiphers);
+        List<Cipher> movedCiphers = new ArrayList<>(ciphers);
         List<Cipher> originalCiphers = new ArrayList<>(ciphers);
-        movedCiphers.retainAll(ciphers);
+        movedCiphers.retainAll(toBeMovedCiphers);
         ciphers.clear();
         ciphers.addAll(movedCiphers);
         ciphers.addAll(originalCiphers);
@@ -569,45 +569,86 @@ public class OpenSSLCipherConfigurationParser {
     }
 
     /*
-     * See 
https://github.com/openssl/openssl/blob/7c96dbcdab959fef74c4caae63cdebaa354ab252/ssl/ssl_ciph.c#L1371
+     * See https://github.com/openssl/openssl/blob/master/ssl/ssl_ciph.c
+     *
+     * Most recently reviewed at afaa70c on 2026-06-12
      */
     static LinkedHashSet<Cipher> defaultSort(final LinkedHashSet<Cipher> 
ciphers) {
-        final LinkedHashSet<Cipher> result = new 
LinkedHashSet<>(ciphers.size());
-        final LinkedHashSet<Cipher> ecdh = new LinkedHashSet<>(ciphers.size());
+        LinkedHashSet<Cipher> result = new LinkedHashSet<>(ciphers.size());
+
+        // Copy because we need to manipulate the order before adding
+        LinkedHashSet<Cipher> source = new LinkedHashSet<>(ciphers);
+
+        // Can't find this in the OpenSSL source but observed in test results
+        Set<Cipher> camelliaWithDSS = filterByAuthentication(source, 
Collections.singleton(Authentication.DSS));
+        camelliaWithDSS = filterByEncryption(
+                camelliaWithDSS, new 
LinkedHashSet<>(Arrays.asList(Encryption.CAMELLIA128, Encryption.CAMELLIA256)));
+        moveToEnd(source, camelliaWithDSS);
+
+        /*
+         * This change is made to source so it effectively applies to each 
group that is subsequently added to the
+         * result.
+         */
+        LinkedHashSet<Cipher> eecdh = filterByKeyExchange(source, 
Collections.singleton(KeyExchange.EECDH));
+        moveToStart(source, eecdh);
+        moveToStart(source, filterByAuthentication(eecdh, 
Collections.singleton(Authentication.ECDSA)));
 
-        /* Everything else being equal, prefer ephemeral ECDH over other key 
exchange mechanisms */
-        ecdh.addAll(filterByKeyExchange(ciphers, 
Collections.singleton(KeyExchange.EECDH)));
+        // Now start adding ciphers to the result
 
-        /* AES is our preferred symmetric cipher */
-        Set<Encryption> aes = new HashSet<>(
-                Arrays.asList(Encryption.AES128, Encryption.AES128CCM, 
Encryption.AES128CCM8, Encryption.AES128GCM,
-                        Encryption.AES256, Encryption.AES256CCM, 
Encryption.AES256CCM8, Encryption.AES256GCM));
+        // Prefer GCM over CHACHA
+        result.addAll(filterByEncryption(source, 
Collections.singleton(Encryption.AES256GCM)));
+        result.addAll(filterByEncryption(source, 
Collections.singleton(Encryption.AES128GCM)));
+        result.addAll(filterByEncryption(source, 
Collections.singleton(Encryption.CHACHA20POLY1305)));
 
-        /* Now arrange all ciphers by preference: */
-        result.addAll(filterByEncryption(ecdh, aes));
-        result.addAll(filterByEncryption(ciphers, aes));
+        // Generally prefer AES
+        result.addAll(filterByEncryption(source,
+                new HashSet<>(Arrays.asList(Encryption.AES256CCM, 
Encryption.AES256CCM8, Encryption.AES256,
+                        Encryption.AES128CCM, Encryption.AES128CCM8, 
Encryption.AES128))));
 
-        /* Add everything else */
-        result.addAll(ecdh);
-        result.addAll(ciphers);
+        // Add everything else
+        result.addAll(source);
 
-        /* Low priority for MD5 */
+        // Move MD5 to end
         moveToEnd(result, filterByMessageDigest(result, 
Collections.singleton(MessageDigest.MD5)));
 
-        /*
-         * Move anonymous ciphers to the end. Usually, these will remain 
disabled. (For applications that allow them,
-         * they aren't too bad, but we prefer authenticated ciphers.)
-         */
+        // Move anonymous ciphers to the end.
         moveToEnd(result, filterByAuthentication(result, 
Collections.singleton(Authentication.aNULL)));
-
-        /* Move ciphers without forward secrecy to the end */
-        moveToEnd(result, filterByAuthentication(result, 
Collections.singleton(Authentication.ECDH)));
         moveToEnd(result, filterByKeyExchange(result, 
Collections.singleton(KeyExchange.RSA)));
         moveToEnd(result, filterByKeyExchange(result, 
Collections.singleton(KeyExchange.PSK)));
 
-        /* RC4 is sort-of broken -- move to the end */
+        // RC4 is sort-of broken -- move to the end
         moveToEnd(result, filterByEncryption(result, 
Collections.singleton(Encryption.RC4)));
-        return strengthSort(result);
+
+        // Sort by encryption strength
+        result = strengthSort(result);
+
+        // Partially override strength sort to prefer TLS 1.2
+        moveToStart(result, filterByProtocol(result, 
Collections.singleton(Protocol.TLSv1_2)));
+
+        /*
+         * Irrespective of strength, enforce the following order:
+         * (EC)DHE + AEAD > (EC)DHE > rest of AEAD > rest.
+         * Within each group, ciphers remain sorted by strength and previous
+         * preference, i.e.,
+         * 1) ECDHE > DHE
+         * 2) GCM > CHACHA
+         * 3) AES > rest
+         * 4) TLS 1.2 > legacy
+         *
+         * Moving to start so move in reverse order.
+         */
+        Set<Cipher> ecdheAndDhe = new LinkedHashSet<>(result.size());
+        Set<Cipher> ecdheAndDheWithAead = new LinkedHashSet<>(result.size());
+
+        ecdheAndDhe.addAll(filterByKeyExchange(result, new 
HashSet<>(Arrays.asList(KeyExchange.EDH, KeyExchange.EECDH))));
+        ecdheAndDheWithAead.addAll(ecdheAndDhe);
+        ecdheAndDheWithAead = filterByMessageDigest(ecdheAndDheWithAead, 
Collections.singleton(MessageDigest.AEAD));
+
+        moveToStart(result, filterByMessageDigest(result, 
Collections.singleton(MessageDigest.AEAD)));
+        moveToStart(result, ecdheAndDhe);
+        moveToStart(result, ecdheAndDheWithAead);
+
+        return result;
     }
 
     static Set<Cipher> filterByStrengthBits(Set<Cipher> ciphers, int 
strength_bits) {
diff --git 
a/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java
 
b/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java
index 41e81cc3e6..cd038cfe93 100644
--- 
a/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java
+++ 
b/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java
@@ -489,7 +489,7 @@ public class TestOpenSSLCipherConfigurationParser {
     public void testSpecification05() throws Exception {
         if (TesterOpenSSL.VERSION < 30200) {
             // OpenSSL 3.2.x moved the CCM8 ciphers from high to medium
-            testSpecification("HIGH:!AESCCM8:!aNULL:!eNULL");
+            testSpecification("HIGH:!AESCCM8:@STRENGTH:!aNULL:!eNULL");
         } else {
             testSpecification("HIGH:@STRENGTH:!aNULL:!eNULL");
         }
@@ -533,16 +533,21 @@ public class TestOpenSSLCipherConfigurationParser {
                         + specification + "'",
                 new TreeSet<>(jsseCipherListFromParser), new 
TreeSet<>(jsseCipherListFromOpenSSL));
 
-        // OpenSSL treats many ciphers as having equal preference. The order
-        // returned depends on the order they are requested. The following code
-        // checks that the Parser produces a cipher list that is consistent 
with
-        // OpenSSL's preference order by confirming that running through 
OpenSSL
-        // does not change the order.
-        String parserOrderedExpression = 
listToString(jsseCipherListFromParser, ',');
-        Assert.assertEquals(
-                listToString(OpenSSLCipherConfigurationParser.parseExpression(
-                        parserOrderedExpression), ','),
-                parserOrderedExpression);
+        /*
+         * The parser generated cipher list is checked for an exact match with 
the list generated by OpenSSL.
+         *
+         * This check tests for the exact order as generated by OpenSSL master 
running on Linux.
+         *
+         * There was a change in the default ordering between 3.2.x and 3.3.x. 
Skip the exact order check if testing
+         * against OpenSSL 3.0.x (the only currently supported version before 
3.3.x).
+         *
+         * It is assumed that the cipher order does not vary by operating 
system. If that assumption is wrong, it should
+         * be caught by CI and the test can be adjusted.
+         */
+        if (TesterOpenSSL.VERSION > 30299) {
+            Assert.assertEquals(specification, 
listToString(jsseCipherListFromOpenSSL, ','),
+                    listToString(jsseCipherListFromParser, ','));
+        }
     }
 
 
diff --git 
a/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParserOnly.java
 
b/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParserOnly.java
index 795e977bf2..d3bc8b8d81 100644
--- 
a/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParserOnly.java
+++ 
b/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParserOnly.java
@@ -83,6 +83,23 @@ public class TestOpenSSLCipherConfigurationParserOnly {
         Assert.assertEquals(expected.toString(), result.toString());
     }
 
+    @Test
+    public void testDefaultSort04() throws Exception {
+        // Reproducing a failure observed when aligning sorting with OpenSSL 
master
+
+        // ECDHE should beat DHE
+        LinkedHashSet<Cipher> input = new LinkedHashSet<>();
+        input.add(Cipher.TLS_DHE_RSA_WITH_AES_256_CCM_8);
+        input.add(Cipher.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8);
+        LinkedHashSet<Cipher> result = 
OpenSSLCipherConfigurationParser.defaultSort(input);
+
+        LinkedHashSet<Cipher> expected = new LinkedHashSet<>();
+        expected.add(Cipher.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8);
+        expected.add(Cipher.TLS_DHE_RSA_WITH_AES_256_CCM_8);
+
+        Assert.assertEquals(expected.toString(), result.toString());
+    }
+
     @Test
     public void testRename01() throws Exception {
         // EDH -> DHE
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 564bc45751..3c64b4006a 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -370,6 +370,10 @@
         <bug>69988</bug>: Fix post handshake authentication for TLS 1.3. It was
         broken by a breaking change in OpenSSL between 1.1.1 and 3.0.0. (markt)
       </fix>
+      <fix>
+        When processing an OpenSSL cipher specification, fully align the order
+        of the resulting ciphers with the order produced by OpenSSL. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Jasper">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to