diff --git a/jdk/src/share/classes/sun/security/util/DisabledAlgorithmConstraints.java b/jdk/src/share/classes/sun/security/util/DisabledAlgorithmConstraints.java index ce3c97120ce..49dd6e7d032 100644 --- a/jdk/src/share/classes/sun/security/util/DisabledAlgorithmConstraints.java +++ b/jdk/src/share/classes/sun/security/util/DisabledAlgorithmConstraints.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. 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 @@ -42,10 +42,11 @@ import java.security.spec.PSSParameterSpec; import java.time.DateTimeException; import java.time.Instant; -import java.time.ZonedDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -57,8 +58,8 @@ import java.util.Collections; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Algorithm constraints for disabled algorithms property @@ -100,6 +101,7 @@ private static class JarHolder { } private final List disabledAlgorithms; + private final List disabledPatterns; private final Constraints algorithmConstraints; private volatile SoftReference> cacheRef = new SoftReference<>(null); @@ -135,6 +137,13 @@ public DisabledAlgorithmConstraints(String propertyName, super(decomposer); disabledAlgorithms = getAlgorithms(propertyName); + // Support patterns only for jdk.tls.disabledAlgorithms + if (PROPERTY_TLS_DISABLED_ALGS.equals(propertyName)) { + disabledPatterns = getDisabledPatterns(); + } else { + disabledPatterns = null; + } + // Check for alias int ecindex = -1, i = 0; for (String s : disabledAlgorithms) { @@ -971,11 +980,48 @@ private boolean cachedCheckAlgorithm(String algorithm) { if (result != null) { return result; } - result = checkAlgorithm(disabledAlgorithms, algorithm, decomposer); + // We won't check patterns if algorithm check fails. + result = checkAlgorithm(disabledAlgorithms, algorithm, decomposer) + && checkDisabledPatterns(algorithm); cache.put(algorithm, result); return result; } + private boolean checkDisabledPatterns(final String algorithm) { + return disabledPatterns == null || disabledPatterns.stream().noneMatch( + p -> p.matcher(algorithm).matches()); + } + + private List getDisabledPatterns() { + List ret = null; + List patternStrings = new ArrayList<>(4); + + for (String p : disabledAlgorithms) { + if (p.contains("*")) { + if (!p.startsWith("TLS_")) { + throw new IllegalArgumentException( + "Wildcard pattern must start with \"TLS_\""); + } + patternStrings.add(p); + } + } + + if (!patternStrings.isEmpty()) { + ret = new ArrayList<>(patternStrings.size()); + + for (String p : patternStrings) { + // Exclude patterns from algorithm code flow. + disabledAlgorithms.remove(p); + + // Ignore all regex characters but asterisk. + ret.add(Pattern.compile( + "^\\Q" + p.replace("*", "\\E.*\\Q") + "\\E$")); + } + } + + return ret; + } + /* * This constraint is used for the complete disabling of the algorithm. */ diff --git a/jdk/src/share/lib/security/java.security-macosx b/jdk/src/share/lib/security/java.security-macosx index 08d5b16ead3..db60088e8a9 100644 --- a/jdk/src/share/lib/security/java.security-macosx +++ b/jdk/src/share/lib/security/java.security-macosx @@ -695,7 +695,11 @@ jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, \ # This is in addition to the jdk.certpath.disabledAlgorithms property above. # # See the specification of "jdk.certpath.disabledAlgorithms" for the -# syntax of the disabled algorithm string. +# syntax of the disabled algorithm string. Additionally, TLS cipher suites +# can be disabled with this property using one or more "*" wildcard characters. +# For example, "TLS_RSA_*" disables all cipher suites that start with +# "TLS_RSA_". Only cipher suites starting with "TLS_" are allowed to have +# wildcard characters. # # Note: The algorithm restrictions do not apply to trust anchors or # self-signed certificates. @@ -705,6 +709,7 @@ jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, \ # # Example: # jdk.tls.disabledAlgorithms=MD5, SSLv3, DSA, RSA keySize < 2048 +# rsa_pkcs1_sha1, secp224r1, TLS_RSA_* jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \ DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \ ECDH, \ @@ -1308,4 +1313,4 @@ jdk.tls.alpnCharset=ISO_8859_1 # # The default pattern value allows any object factory class specified by the reference # instance to recreate the referenced object. -#jdk.jndi.object.factoriesFilter=* \ No newline at end of file +#jdk.jndi.object.factoriesFilter=* diff --git a/jdk/test/sun/security/ssl/CipherSuite/AbstractDisableCipherSuites.java b/jdk/test/sun/security/ssl/CipherSuite/AbstractDisableCipherSuites.java new file mode 100644 index 00000000000..3f428e458ca --- /dev/null +++ b/jdk/test/sun/security/ssl/CipherSuite/AbstractDisableCipherSuites.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. 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. + */ + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSession; + +/** + * This is not a test. Actual tests are implemented by concrete subclasses. + * The abstract class AbstractDisableCipherSuites provides a base framework + * for testing cipher suite disablement. + */ +public abstract class AbstractDisableCipherSuites { + + private static final byte RECTYPE_HS = 0x16; + private static final byte HSMSG_CLIHELLO = 0x01; + private static final ByteBuffer CLIOUTBUF = + ByteBuffer.wrap("Client Side".getBytes()); + + /** + * Create an engine with the default set of cipher suites enabled and make + * sure none of the disabled suites are present in the client hello. + * + * @param disabledSuiteIds the {@code List} of disabled cipher suite IDs + * to be checked for. + * + * @return true if the test passed (No disabled suites), false otherwise + */ + protected boolean testDefaultCase(List disabledSuiteIds) + throws Exception { + System.err.println("\nTest: Default SSLEngine suite set"); + SSLEngine ssle = makeEngine(); + if (getDebug()) { + listCiphers("Suite set upon creation", ssle); + } + SSLEngineResult clientResult; + ByteBuffer cTOs = makeClientBuf(ssle); + clientResult = ssle.wrap(CLIOUTBUF, cTOs); + if (getDebug()) { + dumpResult("ClientHello: ", clientResult); + } + cTOs.flip(); + boolean foundSuite = areSuitesPresentCH(cTOs, disabledSuiteIds); + if (foundSuite) { + System.err.println("FAIL: Found disabled suites!"); + return false; + } else { + System.err.println("PASS: No disabled suites found."); + return true; + } + } + + /** + * Create an engine and set only disabled cipher suites. + * The engine should not create the client hello message since the only + * available suites to assert in the client hello are disabled ones. + * + * @param disabledSuiteNames an array of cipher suite names that + * should be disabled cipher suites. + * + * @return true if the engine throws SSLHandshakeException during client + * hello creation, false otherwise. + */ + protected boolean testEngOnlyDisabled(String[] disabledSuiteNames) + throws Exception { + System.err.println( + "\nTest: SSLEngine configured with only disabled suites"); + try { + SSLEngine ssle = makeEngine(); + ssle.setEnabledCipherSuites(disabledSuiteNames); + if (getDebug()) { + listCiphers("Suite set upon creation", ssle); + } + SSLEngineResult clientResult; + ByteBuffer cTOs = makeClientBuf(ssle); + clientResult = ssle.wrap(CLIOUTBUF, cTOs); + if (getDebug()) { + dumpResult("ClientHello: ", clientResult); + } + cTOs.flip(); + } catch (SSLHandshakeException shse) { + System.err.println("PASS: Caught expected exception: " + shse); + return true; + } + System.err.println("FAIL: Expected SSLHandshakeException not thrown"); + return false; + } + + /** + * Create an engine and add some disabled suites to the default + * set of cipher suites. Make sure none of the disabled suites show up + * in the client hello even though they were explicitly added. + * + * @param disabledNames an array of cipher suite names that + * should be disabled cipher suites. + * @param disabledIds the {@code List} of disabled cipher suite IDs + * to be checked for. + * + * @return true if the test passed (No disabled suites), false otherwise + */ + protected boolean testEngAddDisabled(String[] disabledNames, + List disabledIds) throws Exception { + System.err.println("\nTest: SSLEngine with disabled suites added"); + SSLEngine ssle = makeEngine(); + + // Add disabled suites to the existing engine's set of enabled suites + String[] initialSuites = ssle.getEnabledCipherSuites(); + String[] plusDisSuites = Arrays.copyOf(initialSuites, + initialSuites.length + disabledNames.length); + System.arraycopy(disabledNames, 0, plusDisSuites, + initialSuites.length, disabledNames.length); + ssle.setEnabledCipherSuites(plusDisSuites); + + if (getDebug()) { + listCiphers("Suite set upon creation", ssle); + } + SSLEngineResult clientResult; + ByteBuffer cTOs = makeClientBuf(ssle); + clientResult = ssle.wrap(CLIOUTBUF, cTOs); + if (getDebug()) { + dumpResult("ClientHello: ", clientResult); + } + cTOs.flip(); + boolean foundDisabled = areSuitesPresentCH(cTOs, disabledIds); + if (foundDisabled) { + System.err.println("FAIL: Found disabled suites!"); + return false; + } else { + System.err.println("PASS: No disabled suites found."); + return true; + } + } + + protected String getProtocol() { + return "TLSv1.2"; + } + + private SSLEngine makeEngine() throws GeneralSecurityException { + SSLContext ctx = SSLContext.getInstance(getProtocol()); + ctx.init(null, null, null); + return ctx.createSSLEngine(); + } + + private static ByteBuffer makeClientBuf(SSLEngine ssle) { + ssle.setUseClientMode(true); + ssle.setNeedClientAuth(false); + SSLSession sess = ssle.getSession(); + ByteBuffer cTOs = ByteBuffer.allocateDirect(sess.getPacketBufferSize()); + return cTOs; + } + + private static void listCiphers(String prefix, SSLEngine ssle) { + System.err.println(prefix + "\n---------------"); + String[] suites = ssle.getEnabledCipherSuites(); + for (String suite : suites) { + System.err.println(suite); + } + System.err.println("---------------"); + } + + /** + * Walk a TLS 1.2 or earlier ClientHello looking for any of the suites + * in the suiteIdList. + * + * @param clientHello a ByteBuffer containing the ClientHello message as + * a complete TLS record. The position of the buffer should be + * at the first byte of the TLS record header. + * @param suiteIdList a List of integer values corresponding to + * TLS cipher suite identifiers. + * + * @return true if at least one of the suites in {@code suiteIdList} + * is found in the ClientHello's cipher suite list + * + * @throws IOException if the data in the {@code clientHello} + * buffer is not a TLS handshake message or is not a client hello. + */ + private boolean areSuitesPresentCH(ByteBuffer clientHello, + List suiteIdList) throws IOException { + byte val; + + // Process the TLS Record + val = clientHello.get(); + if (val != RECTYPE_HS) { + throw new IOException( + "Not a handshake record, type = " + val); + } + + // Just skip over the version and length + clientHello.position(clientHello.position() + 4); + + // Check the handshake message type + val = clientHello.get(); + if (val != HSMSG_CLIHELLO) { + throw new IOException( + "Not a ClientHello handshake message, type = " + val); + } + + // Skip over the length + clientHello.position(clientHello.position() + 3); + + // Skip over the protocol version (2) and random (32); + clientHello.position(clientHello.position() + 34); + + // Skip past the session ID (variable length <= 32) + int len = Byte.toUnsignedInt(clientHello.get()); + if (len > 32) { + throw new IOException("Session ID is too large, len = " + len); + } + clientHello.position(clientHello.position() + len); + + // Finally, we are at the cipher suites. Walk the list and place them + // into a List. + int csLen = Short.toUnsignedInt(clientHello.getShort()); + if (csLen % 2 != 0) { + throw new IOException("CipherSuite length is invalid, len = " + + csLen); + } + int csCount = csLen / 2; + List csSuiteList = new ArrayList<>(csCount); + log("Found following suite IDs in hello:"); + for (int i = 0; i < csCount; i++) { + int curSuite = Short.toUnsignedInt(clientHello.getShort()); + log(String.format("Suite ID: 0x%04x", curSuite)); + csSuiteList.add(curSuite); + } + + // Now check to see if any of the suites passed in match what is in + // the suite list. + boolean foundMatch = false; + for (Integer cs : suiteIdList) { + if (csSuiteList.contains(cs)) { + System.err.format("Found match for suite ID 0x%04x\n", cs); + foundMatch = true; + break; + } + } + + // We don't care about the rest of the ClientHello message. + // Rewind and return whether we found a match or not. + clientHello.rewind(); + return foundMatch; + } + + private static void dumpResult(String str, SSLEngineResult result) { + System.err.println("The format of the SSLEngineResult is: \n" + + "\t\"getStatus() / getHandshakeStatus()\" +\n" + + "\t\"bytesConsumed() / bytesProduced()\"\n"); + HandshakeStatus hsStatus = result.getHandshakeStatus(); + System.err.println(str + result.getStatus() + "/" + hsStatus + ", " + + result.bytesConsumed() + "/" + result.bytesProduced() + " bytes"); + if (hsStatus == HandshakeStatus.FINISHED) { + System.err.println("\t...ready for application data"); + } + } + + private void log(String str) { + if (getDebug()) { + System.err.println(str); + } + } + + protected boolean getDebug() { + return false; + } +} diff --git a/jdk/test/sun/security/ssl/CipherSuite/TLSCipherSuiteWildCardMatchingDisablePartsOfCipherSuite.java b/jdk/test/sun/security/ssl/CipherSuite/TLSCipherSuiteWildCardMatchingDisablePartsOfCipherSuite.java new file mode 100644 index 00000000000..228924cf46e --- /dev/null +++ b/jdk/test/sun/security/ssl/CipherSuite/TLSCipherSuiteWildCardMatchingDisablePartsOfCipherSuite.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. 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 8341964 + * @summary Add mechanism to disable different parts of TLS cipher suite + * @run testng/othervm TLSCipherSuiteWildCardMatchingDisablePartsOfCipherSuite + */ + +import static org.testng.AssertJUnit.assertTrue; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.security.Security; +import java.util.Arrays; +import java.util.List; + +public class TLSCipherSuiteWildCardMatchingDisablePartsOfCipherSuite extends + AbstractDisableCipherSuites { + + private static final String SECURITY_PROPERTY = "jdk.tls.disabledAlgorithms"; + private static final String TEST_ALGORITHMS = + "TLS_RSA_*," + + " TLS_ECDH*WITH_AES_256_GCM_*," + + " TLS_*_anon_WITH_AES_*_SHA," + + " TLS_.*"; // This pattern should not disable anything + private static final String[] CIPHER_SUITES = new String[] { + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "TLS_ECDH_anon_WITH_AES_256_CBC_SHA" + }; + static final List CIPHER_SUITES_IDS = Arrays.asList( + 0x009D, + 0x009C, + 0x003D, + 0xC02E, + 0xC02C, + 0x0034, + 0xC018 + ); + + @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)); + } +} diff --git a/jdk/test/sun/security/ssl/CipherSuite/TLSCipherSuiteWildCardMatchingIllegalArgument.java b/jdk/test/sun/security/ssl/CipherSuite/TLSCipherSuiteWildCardMatchingIllegalArgument.java new file mode 100644 index 00000000000..d9894868337 --- /dev/null +++ b/jdk/test/sun/security/ssl/CipherSuite/TLSCipherSuiteWildCardMatchingIllegalArgument.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. 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 8341964 + * @summary Add mechanism to disable different parts of TLS cipher suite + * @run testng/othervm TLSCipherSuiteWildCardMatchingIllegalArgument + */ + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.fail; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.security.Security; + +import javax.net.ssl.SSLContext; + +/** + * SSLContext loads "jdk.tls.disabledAlgorithms" system property statically + * when it's being loaded into memory, so we can't call + * Security.setProperty("jdk.tls.disabledAlgorithms") more than once per test + * class. Thus, we need a separate test class each time we need to modify + * "jdk.tls.disabledAlgorithms" config value for testing. + */ +public class TLSCipherSuiteWildCardMatchingIllegalArgument { + + private static final String SECURITY_PROPERTY = + "jdk.tls.disabledAlgorithms"; + private static final String TEST_ALGORITHMS = "ECDHE_*_WITH_AES_256_GCM_*"; + + @BeforeTest + void setUp() throws Exception { + Security.setProperty(SECURITY_PROPERTY, TEST_ALGORITHMS); + } + + @Test + public void testChainedBefore() throws Exception { + try { + SSLContext.getInstance("TLS"); + fail("No IllegalArgumentException was thrown"); + } catch (ExceptionInInitializerError e) { + assertEquals(IllegalArgumentException.class, + e.getCause().getClass()); + assertEquals("Wildcard pattern must start with \"TLS_\"", + e.getCause().getMessage()); + } + } +}