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

rmaucher pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 1d79e125f9e91773b7ad54316a0c4d63a3e902d7
Author: opencode <[email protected]>
AuthorDate: Fri Oct 16 08:32:57 2026 +0200

    Match OpenSSL semantics for AND cipher expressions with unknown terms
    
    In the OpenSSL cipher configuration parser, an AND expression (X+Y+Z)
    that contains an unknown term is only evaluated when every term is a
    known alias. OpenSSL rejects such an element entirely (the equivalent
    cipher list string produces no cipher match).
    
    Previously, the Java parser only required the first term to be a known
    alias and silently skipped any subsequent unknown term. This made the
    result position dependent and silently widened the configured cipher
    set, e.g. parse("SHA1+Nonsense") returned the full SHA1 set while
    parse("Nonsense+SHA1") returned no ciphers at all. A typo in the
    second or later term of an AND expression therefore loosened the
    configured cipher set with no indication in the logs.
    
    The parser now logs the unknown element as a warning and discards the
    whole element, matching OpenSSL behavior regardless of the position of
    the unknown term.
    
    TestCipher builds synthetic NAME+PROTOCOL tokens from the columns of
    openssl ciphers -v output for its mapping checks. The SSLv3 protocol
    suffix is no longer a keyword the parser supports (removed earlier to
    align with the OpenSSL development branch) and previously relied on the
    lenient AND handling. The protocol suffix is informational for these
    tokens, so the test now strips a trailing +SSLv3 suffix before parsing
    while continuing to use the full token for the unmapped-cipher
    bookkeeping. A new test asserts an AND expression with an unknown term
    parses to an empty set for both term orderings.
---
 .../ciphers/OpenSSLCipherConfigurationParser.java   | 21 +++++++++++++++------
 .../tomcat/util/net/openssl/ciphers/TestCipher.java | 11 ++++++++++-
 .../TestOpenSSLCipherConfigurationParser.java       | 10 ++++++++++
 webapps/docs/changelog.xml                          |  7 +++++++
 4 files changed, 42 insertions(+), 7 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 0f85779ea9..157d716a47 100644
--- 
a/java/org/apache/tomcat/util/net/openssl/ciphers/OpenSSLCipherConfigurationParser.java
+++ 
b/java/org/apache/tomcat/util/net/openssl/ciphers/OpenSSLCipherConfigurationParser.java
@@ -810,13 +810,22 @@ public class OpenSSLCipherConfigurationParser {
                 add(ciphers, element, orderedAllAddedCiphers);
             } else if (element.contains(AND)) {
                 String[] intersections = element.split("\\" + AND);
-                if (intersections.length > 0 && 
aliases.containsKey(intersections[0])) {
-                    List<Cipher> result = new 
ArrayList<>(aliases.get(intersections[0]));
-                    for (int i = 1; i < intersections.length; i++) {
-                        if (aliases.containsKey(intersections[i])) {
-                            result.retainAll(aliases.get(intersections[i]));
-                        }
+                List<Cipher> result = null;
+                for (String intersection : intersections) {
+                    // As in OpenSSL, any unknown term makes the whole AND
+                    // element match nothing
+                    if (!aliases.containsKey(intersection)) {
+                        
log.warn(sm.getString("opensslCipherConfigurationParser.unknownElement", 
intersection));
+                        result = null;
+                        break;
                     }
+                    if (result == null) {
+                        result = new ArrayList<>(aliases.get(intersection));
+                    } else {
+                        result.retainAll(aliases.get(intersection));
+                    }
+                }
+                if (result != null) {
                     add(ciphers, result, orderedAllAddedCiphers);
                 }
             }
diff --git a/test/org/apache/tomcat/util/net/openssl/ciphers/TestCipher.java 
b/test/org/apache/tomcat/util/net/openssl/ciphers/TestCipher.java
index 4753a4814e..46309bf581 100644
--- a/test/org/apache/tomcat/util/net/openssl/ciphers/TestCipher.java
+++ b/test/org/apache/tomcat/util/net/openssl/ciphers/TestCipher.java
@@ -39,8 +39,17 @@ public class TestCipher {
         StringBuilder errors = new StringBuilder();
 
         for (String openSSLCipherSuite : openSSLCipherSuites) {
+            // The protocol suffix identifies the version OpenSSL reports as
+            // the minimum protocol for the cipher suite. SSLv3 is no longer a
+            // supported alias in cipher configuration strings (removed from
+            // OpenSSL and from this parser) so strip that suffix before
+            // parsing. The suffix is not used to determine the mapping.
+            String specification = openSSLCipherSuite;
+            if (specification.endsWith("+SSLv3")) {
+                specification = specification.substring(0, 
specification.length() - "+SSLv3".length());
+            }
             List<String> jsseCipherSuites =
-                    
OpenSSLCipherConfigurationParser.parseExpression(openSSLCipherSuite);
+                    
OpenSSLCipherConfigurationParser.parseExpression(specification);
 
             for (JsseImpl jsseImpl : JSSE_IMPLS) {
                 boolean found = false;
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 a01e8ba384..7f8cf44f65 100644
--- 
a/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java
+++ 
b/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java
@@ -537,6 +537,16 @@ public class TestOpenSSLCipherConfigurationParser {
     }
 
 
+    @Test
+    public void testIntersectionWithUnknownTermIsEmpty() throws Exception {
+        // OpenSSL treats an AND element that contains an unknown term as
+        // matching no ciphers, regardless of the position of the unknown term
+        testSpecificationIsEmpty("SHA1+Nonsense");
+        Assert.assertEquals(0, 
OpenSSLCipherConfigurationParser.parse("SHA1+Nonsense").size());
+        Assert.assertEquals(0, 
OpenSSLCipherConfigurationParser.parse("Nonsense+SHA1").size());
+    }
+
+
     private void testSpecificationIsEmpty(String specification) throws 
Exception {
         String openSSLCipherList = 
TesterOpenSSL.getOpenSSLCiphersAsExpression(specification);
         Assert.assertEquals(TesterOpenSSL.VERSION_STRING + " with 
specification [" + specification + "] returned [" +
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 9701a4558c..7a2d8143f3 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -333,6 +333,13 @@
         drop TLS 1.3 when rebuilding the list of enabled protocols during
         initialization. (dsoumis)
       </fix>
+      <fix>
+        In the OpenSSL cipher configuration parser, treat an AND expression
+        (<code>+</code>) that contains an unknown term as matching no ciphers
+        and log a warning, rather than silently ignoring the unknown term and
+        using only the known terms. This matches OpenSSL behavior, where such
+        an expression results in no cipher match. (remm)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Web applications">


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

Reply via email to