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]
