8387124: Incomplete algorithm decomposition for TLS 1.3 cipher suites in SSLAlgorithmDecomposer

Reviewed-by: abarashev, mullan
This commit is contained in:
Andreas Chmielewski 2026-07-01 20:32:46 +00:00 committed by Artur Barashev
parent 0c209afd4f
commit 3f52251c9d
3 changed files with 307 additions and 0 deletions

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2026, IBM Corporation. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -172,9 +173,18 @@ class SSLAlgorithmDecomposer extends AlgorithmDecomposer {
case B_AES_128_GCM:
components.add("AES_128_GCM");
break;
case B_AES_128_GCM_IV:
components.add("AES_128_GCM");
break;
case B_AES_256_GCM:
components.add("AES_256_GCM");
break;
case B_AES_256_GCM_IV:
components.add("AES_256_GCM");
break;
case B_CC20_P1305:
components.add("CHACHA20_POLY1305");
break;
}
return components;

View File

@ -0,0 +1,218 @@
/*
* Copyright (c) 2026, IBM Corporation. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8387124
* @summary Test TLS cipher suite disabling via jdk.tls.disabledAlgorithms,
* including matching on bulk cipher components, covering both
* visibility and handshake behavior.
* @library /test/lib
* /javax/net/ssl/TLSCommon
* /javax/net/ssl/templates
* @run main/othervm BulkCipherDisabledAlgorithms visibility
* @run main/othervm BulkCipherDisabledAlgorithms handshake
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.*;
import jdk.test.lib.process.Proc;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
public class BulkCipherDisabledAlgorithms {
public static void main(String[] args) throws Exception {
if (args.length == 0) {
throw new RuntimeException("Missing mode argument");
}
String mode = args[0];
boolean isVisibilityTest = "visibility".equals(mode);
boolean isHandshakeTest = "handshake".equals(mode);
if (args.length == 1) {
List<String[]> tests = buildTests(isVisibilityTest);
for (String[] test : tests) {
String suite = test[0];
String disabled = test[1];
String expected = test[2];
System.out.println("=================================================");
System.out.println("Testing: " + mode +
", suite=" + suite +
", disabled=" + disabled +
", expected=" + expected);
Proc p = Proc.create(
BulkCipherDisabledAlgorithms.class.getName())
.args(mode, suite, expected)
.secprop("jdk.tls.disabledAlgorithms", disabled)
.inheritIO();
p.start().waitFor(0);
}
System.out.println("TEST PASS - OK");
return;
}
String suite = args[1];
String expected = args[2];
boolean expectedDisabled = "disabled".equals(expected);
if (isVisibilityTest) {
testCipherSuiteVisibility(suite, expectedDisabled);
}
if (isHandshakeTest) {
testHandshake(suite, expectedDisabled);
}
}
// Returns cipher suites for testing.
// - true: use all supported suites (independent of disabledAlgorithms)
// - false: use default enabled suites (candidates for handshake)
private static CipherSuite[] getCipherSuites(boolean useSupportedSuites)
throws NoSuchAlgorithmException {
SSLEngine engine = SSLContext.getDefault().createSSLEngine();
String[] suites = useSupportedSuites
? engine.getSupportedCipherSuites()
: engine.getEnabledCipherSuites();
return Arrays.stream(suites)
.map(CipherSuite::cipherSuite)
.filter(cs -> cs != CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
.toArray(CipherSuite[]::new);
}
private static List<String[]> buildTests(boolean useSupportedSuites)
throws NoSuchAlgorithmException {
if (useSupportedSuites) {
// disabledAlgorithms limits supported suites; clear to list all
Security.setProperty("jdk.tls.disabledAlgorithms", "");
}
List<String[]> tests = new ArrayList<>();
CipherSuite[] suites = getCipherSuites(useSupportedSuites);
for (CipherSuite suite : suites) {
String suiteName = suite.name();
String bulk = extractBulkCipher(suiteName);
tests.add(new String[] { suiteName, suiteName, "disabled" });
tests.add(new String[] { suiteName, bulk, "disabled" });
for (CipherSuite other : suites) {
// Negative test case: disable a different bulk cipher than the one
// used by the current suite. This ensures that the suite remains
// enabled and a successful TLS handshake can still be negotiated.
if (other == suite) {
continue;
}
String otherBulk = extractBulkCipher(other.name());
if (!bulk.equals(otherBulk)
&& !suiteName.contains(otherBulk)) {
tests.add(new String[] { suiteName, otherBulk, "enabled" });
break;
}
}
}
return tests;
}
/**
* Separator used in TLS cipher suite names to mark the start of
* the bulk cipher component (e.g. TLS_RSA_WITH_AES_128_CBC_SHA).
*/
private static final String WITH = "_WITH_";
private static String extractBulkCipher(String suite) {
if (suite.contains(WITH)) {
String after = suite.substring(suite.indexOf(WITH) + WITH.length());
int last = after.lastIndexOf('_');
return after.substring(0, last);
} else {
int first = suite.indexOf('_');
int last = suite.lastIndexOf('_');
return suite.substring(first + 1, last);
}
}
private static void testCipherSuiteVisibility(String suite, boolean expectedDisabled)
throws NoSuchAlgorithmException {
boolean visible = Arrays.asList(getCipherSuites(true))
.contains(CipherSuite.cipherSuite(suite));
if (!expectedDisabled && !visible) {
throw new RuntimeException(
"Cipher suite '" + suite + "' not visible but expected to be enabled");
} else if (expectedDisabled && visible) {
throw new RuntimeException(
"Cipher suite '" + suite + "' visible but expected to be disabled");
}
}
private static void testHandshake(String suite, boolean expectedDisabled) throws Exception {
try {
new TLSHandshakeTest(suite).run();
if (expectedDisabled) {
throw new RuntimeException(
"Handshake succeeded but should fail: " + suite);
}
} catch (SSLHandshakeException e) {
if (!expectedDisabled) {
throw new RuntimeException(
"Handshake failed unexpectedly: " + suite, e);
}
}
}
private static class TLSHandshakeTest extends SSLSocketTemplate {
private final String suite;
TLSHandshakeTest(String suite) {
this.suite = suite;
}
@Override
protected void configureClientSocket(SSLSocket socket) {
socket.setEnabledCipherSuites(new String[] { suite });
}
@Override
protected void configureServerSocket(SSLServerSocket socket) {
socket.setEnabledCipherSuites(new String[] { suite });
}
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2026, IBM Corporation. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8387124
* @summary Test disabling TLS 1.3 cipher suites with bulk ciphers names
* @run testng/othervm TLS13BulkCipherDisabledCipherSuite
*/
import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.security.Security;
import java.util.List;
public class TLS13BulkCipherDisabledCipherSuite extends AbstractDisableCipherSuites {
private static final String SECURITY_PROPERTY = "jdk.tls.disabledAlgorithms";
private static final String TEST_ALGORITHMS = "AES_256_GCM,"
+ " AES_128_GCM,"
+ " CHACHA20_POLY1305";
private static final String[] CIPHER_SUITES = new String[] {
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256"
};
static final List<Integer> CIPHER_SUITES_IDS = List.of(
0x1301,
0x1302,
0x1303);
@Override
protected String getProtocol() {
return "TLSv1.3";
}
@BeforeTest
void setUp() throws Exception {
Security.setProperty(SECURITY_PROPERTY, TEST_ALGORITHMS);
}
@Test
public void testDefault() throws Exception {
assertTrue(testDefaultCase(CIPHER_SUITES_IDS));
}
@Test
public void testAddDisabled() throws Exception {
assertTrue(testEngAddDisabled(CIPHER_SUITES, CIPHER_SUITES_IDS));
}
@Test
public void testOnlyDisabled() throws Exception {
assertTrue(testEngOnlyDisabled(CIPHER_SUITES));
}
}