From 5daf0666e9c0fc239d1ba8b63ecb494e367b06f2 Mon Sep 17 00:00:00 2001
From: Goetz Lindenmaier <goetz@openjdk.org>
Date: Fri, 25 Oct 2024 07:37:34 +0000
Subject: [PATCH] 8301379: Verify TLS_ECDH_* cipher suites cannot be negotiated

Reviewed-by: lucy
Backport-of: 9f64a64376c677dbe1358807329b42737ac78ad9
---
 .../ssl/ciphersuites/DisabledAlgorithms.java  | 216 ++++++++----------
 .../TLSWontNegotiateDisabledCipherAlgos.java  | 146 ++++++++++++
 2 files changed, 245 insertions(+), 117 deletions(-)
 create mode 100644 test/jdk/javax/net/ssl/ciphersuites/TLSWontNegotiateDisabledCipherAlgos.java

diff --git a/test/jdk/javax/net/ssl/ciphersuites/DisabledAlgorithms.java b/test/jdk/javax/net/ssl/ciphersuites/DisabledAlgorithms.java
index 855e34b57f0..1dd8be84785 100644
--- a/test/jdk/javax/net/ssl/ciphersuites/DisabledAlgorithms.java
+++ b/test/jdk/javax/net/ssl/ciphersuites/DisabledAlgorithms.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2023, 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
@@ -25,6 +25,7 @@
  * @test
  * @bug 8076221 8211883 8279164
  * @summary Check if weak cipher suites are disabled
+ * @library /javax/net/ssl/templates
  * @modules jdk.crypto.ec
  * @run main/othervm DisabledAlgorithms default
  * @run main/othervm DisabledAlgorithms empty
@@ -35,7 +36,6 @@ import java.io.BufferedOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.security.NoSuchAlgorithmException;
 import java.security.Security;
 import java.util.concurrent.TimeUnit;
 import javax.net.ssl.SSLContext;
@@ -45,68 +45,72 @@ import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 
+/*
+ * This test verifies that setEnabledProtocols() does not override the
+ * jdk.tls.disabledAlgorithms property. Both the client and server throw
+ * an exception when creating a handshake context.
+ *
+ * In the TLSWontNegotiateDisabledCipherAlgoos test, one side of the connection
+ * disables the cipher suites and the other side enables them and verifies
+ * that the handshake cannot complete successfully.
+ */
 public class DisabledAlgorithms {
 
-    private static final String pathToStores = "../etc";
-    private static final String keyStoreFile = "keystore";
-    private static final String trustStoreFile = "truststore";
-    private static final String passwd = "passphrase";
-
-    private static final String keyFilename =
-            System.getProperty("test.src", "./") + "/" + pathToStores +
-                "/" + keyStoreFile;
-
-    private static final String trustFilename =
-            System.getProperty("test.src", "./") + "/" + pathToStores +
-                "/" + trustStoreFile;
+    public static final SSLContextTemplate.Cert[] CERTIFICATES = {
+            SSLContextTemplate.Cert.EE_DSA_SHA1_1024,
+            SSLContextTemplate.Cert.EE_DSA_SHA224_1024,
+            SSLContextTemplate.Cert.EE_DSA_SHA256_1024,
+            SSLContextTemplate.Cert.CA_ECDSA_SECP256R1,
+            SSLContextTemplate.Cert.CA_RSA_2048
+    };
 
     // disabled RC4, NULL, anon, and ECDH cipher suites
-    private static final String[] disabled_ciphersuites
-        = new String[] {
-        "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
-        "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
-        "SSL_RSA_WITH_RC4_128_SHA",
-        "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
-        "TLS_ECDH_RSA_WITH_RC4_128_SHA",
-        "SSL_RSA_WITH_RC4_128_MD5",
-        "TLS_ECDH_anon_WITH_RC4_128_SHA",
-        "SSL_DH_anon_WITH_RC4_128_MD5",
-        "SSL_RSA_WITH_NULL_MD5",
-        "SSL_RSA_WITH_NULL_SHA",
-        "TLS_RSA_WITH_NULL_SHA256",
-        "TLS_ECDH_ECDSA_WITH_NULL_SHA",
-        "TLS_ECDHE_ECDSA_WITH_NULL_SHA",
-        "TLS_ECDH_RSA_WITH_NULL_SHA",
-        "TLS_ECDHE_RSA_WITH_NULL_SHA",
-        "TLS_ECDH_anon_WITH_NULL_SHA",
-        "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
-        "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
-        "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
-        "SSL_DH_anon_WITH_DES_CBC_SHA",
-        "SSL_DH_anon_WITH_RC4_128_MD5",
-        "TLS_DH_anon_WITH_AES_128_CBC_SHA",
-        "TLS_DH_anon_WITH_AES_128_CBC_SHA256",
-        "TLS_DH_anon_WITH_AES_128_GCM_SHA256",
-        "TLS_DH_anon_WITH_AES_256_CBC_SHA",
-        "TLS_DH_anon_WITH_AES_256_CBC_SHA256",
-        "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
-        "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
-        "TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
-        "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
-        "TLS_ECDH_anon_WITH_NULL_SHA",
-        "TLS_ECDH_anon_WITH_RC4_128_SHA",
-        "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
-        "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
-        "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
-        "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
-        "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
-        "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
-        "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
-        "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
-        "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
-        "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
-        "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
-        "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA"
+    public static final String[] DISABLED_CIPHERSUITES
+            = new String[]{
+            "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
+            "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
+            "SSL_RSA_WITH_RC4_128_SHA",
+            "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
+            "TLS_ECDH_RSA_WITH_RC4_128_SHA",
+            "SSL_RSA_WITH_RC4_128_MD5",
+            "TLS_ECDH_anon_WITH_RC4_128_SHA",
+            "SSL_DH_anon_WITH_RC4_128_MD5",
+            "SSL_RSA_WITH_NULL_MD5",
+            "SSL_RSA_WITH_NULL_SHA",
+            "TLS_RSA_WITH_NULL_SHA256",
+            "TLS_ECDH_ECDSA_WITH_NULL_SHA",
+            "TLS_ECDHE_ECDSA_WITH_NULL_SHA",
+            "TLS_ECDH_RSA_WITH_NULL_SHA",
+            "TLS_ECDHE_RSA_WITH_NULL_SHA",
+            "TLS_ECDH_anon_WITH_NULL_SHA",
+            "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
+            "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
+            "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
+            "SSL_DH_anon_WITH_DES_CBC_SHA",
+            "SSL_DH_anon_WITH_RC4_128_MD5",
+            "TLS_DH_anon_WITH_AES_128_CBC_SHA",
+            "TLS_DH_anon_WITH_AES_128_CBC_SHA256",
+            "TLS_DH_anon_WITH_AES_128_GCM_SHA256",
+            "TLS_DH_anon_WITH_AES_256_CBC_SHA",
+            "TLS_DH_anon_WITH_AES_256_CBC_SHA256",
+            "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
+            "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
+            "TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
+            "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
+            "TLS_ECDH_anon_WITH_NULL_SHA",
+            "TLS_ECDH_anon_WITH_RC4_128_SHA",
+            "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
+            "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
+            "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
+            "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
+            "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
+            "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
+            "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
+            "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
+            "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
+            "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
+            "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
+            "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA"
     };
 
     public static void main(String[] args) throws Exception {
@@ -114,11 +118,6 @@ public class DisabledAlgorithms {
             throw new RuntimeException("No parameters specified");
         }
 
-        System.setProperty("javax.net.ssl.keyStore", keyFilename);
-        System.setProperty("javax.net.ssl.keyStorePassword", passwd);
-        System.setProperty("javax.net.ssl.trustStore", trustFilename);
-        System.setProperty("javax.net.ssl.trustStorePassword", passwd);
-
         switch (args[0]) {
             case "default":
                 // use default jdk.tls.disabledAlgorithms
@@ -126,7 +125,7 @@ public class DisabledAlgorithms {
                         + Security.getProperty("jdk.tls.disabledAlgorithms"));
 
                 // check that disabled cipher suites can't be used by default
-                checkFailure(disabled_ciphersuites);
+                checkFailure(DISABLED_CIPHERSUITES);
                 break;
             case "empty":
                 // reset jdk.tls.disabledAlgorithms
@@ -136,7 +135,7 @@ public class DisabledAlgorithms {
 
                 // check that disabled cipher suites can be used if
                 // jdk.{tls,certpath}.disabledAlgorithms is empty
-                checkSuccess(disabled_ciphersuites);
+                checkSuccess(DISABLED_CIPHERSUITES);
                 break;
             default:
                 throw new RuntimeException("Wrong parameter: " + args[0]);
@@ -149,7 +148,7 @@ public class DisabledAlgorithms {
      * Checks if that specified cipher suites cannot be used.
      */
     private static void checkFailure(String[] ciphersuites) throws Exception {
-        try (SSLServer server = SSLServer.init(ciphersuites)) {
+        try (SSLServer server = new SSLServer(ciphersuites)) {
             startNewThread(server);
             while (!server.isRunning()) {
                 sleep();
@@ -157,7 +156,7 @@ public class DisabledAlgorithms {
 
             int port = server.getPort();
             for (String ciphersuite : ciphersuites) {
-                try (SSLClient client = SSLClient.init(port, ciphersuite)) {
+                try (SSLClient client = new SSLClient(port, ciphersuite)) {
                     client.connect();
                     throw new RuntimeException("Expected SSLHandshakeException "
                             + "not thrown");
@@ -184,7 +183,7 @@ public class DisabledAlgorithms {
      * Checks if specified cipher suites can be used.
      */
     private static void checkSuccess(String[] ciphersuites) throws Exception {
-        try (SSLServer server = SSLServer.init(ciphersuites)) {
+        try (SSLServer server = new SSLServer(ciphersuites)) {
             startNewThread(server);
             while (!server.isRunning()) {
                 sleep();
@@ -192,7 +191,7 @@ public class DisabledAlgorithms {
 
             int port = server.getPort();
             for (String ciphersuite : ciphersuites) {
-                try (SSLClient client = SSLClient.init(port, ciphersuite)) {
+                try (SSLClient client = new SSLClient(port, ciphersuite)) {
                     client.connect();
                     String negotiated = client.getNegotiatedCipherSuite();
                     System.out.println("Negotiated cipher suite: "
@@ -231,7 +230,8 @@ public class DisabledAlgorithms {
         }
     }
 
-    static class SSLServer implements Runnable, AutoCloseable {
+    static class SSLServer extends SSLContextTemplate implements Runnable, AutoCloseable {
+
 
         private final SSLServerSocket ssocket;
         private volatile boolean stopped = false;
@@ -239,7 +239,19 @@ public class DisabledAlgorithms {
         private volatile boolean sslError = false;
         private volatile boolean otherError = false;
 
-        private SSLServer(SSLServerSocket ssocket) {
+        private SSLServer(String[] ciphersuites) throws Exception {
+            SSLContext context = createSSLContext(null,
+                    DisabledAlgorithms.CERTIFICATES, getServerContextParameters());
+            SSLServerSocketFactory ssf = context.getServerSocketFactory();
+            SSLServerSocket ssocket = (SSLServerSocket)
+                    ssf.createServerSocket(0);
+
+            if (ciphersuites != null) {
+                System.out.println("Server: enable cipher suites: "
+                        + java.util.Arrays.toString(ciphersuites));
+                ssocket.setEnabledCipherSuites(ciphersuites);
+            }
+
             this.ssocket = ssocket;
         }
 
@@ -273,8 +285,8 @@ public class DisabledAlgorithms {
                     } else {
                         System.out.println("Server: run: " + e);
                         System.out.println("The exception above occurred "
-                                    + "because socket was closed, "
-                                    + "please ignore it");
+                                + "because socket was closed, "
+                                + "please ignore it");
                     }
                 }
             }
@@ -319,29 +331,23 @@ public class DisabledAlgorithms {
         public void close() {
             stop();
         }
-
-        static SSLServer init(String[] ciphersuites)
-                throws IOException {
-            SSLServerSocketFactory ssf = (SSLServerSocketFactory)
-                    SSLServerSocketFactory.getDefault();
-            SSLServerSocket ssocket = (SSLServerSocket)
-                    ssf.createServerSocket(0);
-
-            if (ciphersuites != null) {
-                System.out.println("Server: enable cipher suites: "
-                        + java.util.Arrays.toString(ciphersuites));
-                ssocket.setEnabledCipherSuites(ciphersuites);
-            }
-
-            return new SSLServer(ssocket);
-        }
     }
 
-    static class SSLClient implements AutoCloseable {
+    static class SSLClient extends SSLContextTemplate implements AutoCloseable {
 
         private final SSLSocket socket;
 
-        private SSLClient(SSLSocket socket) {
+        private SSLClient(int port, String ciphersuite) throws Exception {
+            SSLContext context = createSSLContext(DisabledAlgorithms.CERTIFICATES,
+                    null, getClientContextParameters());
+            SSLSocketFactory ssf = context.getSocketFactory();
+            SSLSocket socket = (SSLSocket) ssf.createSocket("localhost", port);
+
+            if (ciphersuite != null) {
+                System.out.println("Client: enable cipher suite: "
+                        + ciphersuite);
+                socket.setEnabledCipherSuites(new String[]{ciphersuite});
+            }
             this.socket = socket;
         }
 
@@ -381,29 +387,5 @@ public class DisabledAlgorithms {
                 }
             }
         }
-
-        static SSLClient init(int port)
-                throws NoSuchAlgorithmException, IOException {
-            return init(port, null);
-        }
-
-        static SSLClient init(int port, String ciphersuite)
-                throws NoSuchAlgorithmException, IOException {
-            SSLContext context = SSLContext.getDefault();
-            SSLSocketFactory ssf = (SSLSocketFactory)
-                    context.getSocketFactory();
-            SSLSocket socket = (SSLSocket) ssf.createSocket("localhost", port);
-
-            if (ciphersuite != null) {
-                System.out.println("Client: enable cipher suite: "
-                        + ciphersuite);
-                socket.setEnabledCipherSuites(new String[] { ciphersuite });
-            }
-
-            return new SSLClient(socket);
-        }
-
     }
-
-
 }
diff --git a/test/jdk/javax/net/ssl/ciphersuites/TLSWontNegotiateDisabledCipherAlgos.java b/test/jdk/javax/net/ssl/ciphersuites/TLSWontNegotiateDisabledCipherAlgos.java
new file mode 100644
index 00000000000..b120f33da94
--- /dev/null
+++ b/test/jdk/javax/net/ssl/ciphersuites/TLSWontNegotiateDisabledCipherAlgos.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2023, 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 javax.net.ssl.*;
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.security.Security;
+import java.util.List;
+
+/*
+ * @test id=Server
+ * @bug 8301379
+ * @summary Verify that Java will not negotiate disabled cipher suites when the
+ * other side of the connection requests them.
+ *
+ * @library /javax/net/ssl/templates
+ * @run main/othervm TLSWontNegotiateDisabledCipherAlgos server true
+ */
+
+/*
+ * @test id=Client
+ * @bug 8301379
+ * @summary Verify that Java will not negotiate disabled cipher suites when the
+ * other side of the connection requests them.
+ *
+ * @library /javax/net/ssl/templates
+ * @run main/othervm TLSWontNegotiateDisabledCipherAlgos server false
+ */
+
+
+public class TLSWontNegotiateDisabledCipherAlgos {
+
+    public static void main(String [] args) throws Exception {
+        boolean useDisabledAlgo = Boolean.parseBoolean(args[1]);
+        if (useDisabledAlgo) {
+            Security.setProperty("jdk.tls.disabledAlgorithms", "");
+        }
+
+        if (args[0].equals("server")) {
+            try (TLSServer server = new TLSServer(useDisabledAlgo)) {
+                List<String> command = List.of(
+                        Path.of(System.getProperty("java.home"), "bin", "java").toString(),
+                        "TLSWontNegotiateDisabledCipherAlgos",
+                        "client",
+                        Boolean.toString(!useDisabledAlgo),
+                        Integer.toString(server.getListeningPort())
+                );
+                ProcessBuilder builder = new ProcessBuilder(command);
+                Process p = builder.inheritIO().start();
+                server.run();
+                p.destroy();
+            }
+        } else if (args[0].equals("client")) {
+            try (TLSClient client = new TLSClient(Integer.parseInt(args[2]), useDisabledAlgo)) {
+                client.run();
+            }
+        }
+    }
+
+    private static class TLSClient extends SSLContextTemplate implements AutoCloseable {
+        private final SSLSocket socket;
+
+        public TLSClient(int portNumber, boolean useDisableAlgo) throws Exception {
+            SSLContext context = createClientSSLContext();
+            socket = (SSLSocket)context.getSocketFactory().createSocket("localhost", portNumber);
+            if (useDisableAlgo) {
+                socket.setEnabledCipherSuites(DisabledAlgorithms.DISABLED_CIPHERSUITES);
+            }
+        }
+
+        public void run() throws IOException {
+            try {
+                socket.getOutputStream().write("SECRET MESSAGE".getBytes(StandardCharsets.UTF_8));
+                throw new RuntimeException("SSL handshake completed successfully.");
+            } catch (SSLHandshakeException exc) {
+                if (!exc.getMessage().equals("Received fatal alert: handshake_failure")) {
+                    throw new RuntimeException("Expected handshake_failure message. Got: "
+                            + "\"" + exc.getMessage() + "\" message.", exc);
+                }
+            }
+        }
+
+        @Override
+        public void close() throws Exception {
+            socket.close();
+        }
+    }
+
+    private static class TLSServer extends SSLContextTemplate implements AutoCloseable {
+        private SSLServerSocket serverSocket;
+
+        public TLSServer(boolean useDisableAlgo) throws Exception {
+            SSLContext ctx = createServerSSLContext();
+            serverSocket = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(0);
+            if (useDisableAlgo) {
+                serverSocket.setEnabledCipherSuites(DisabledAlgorithms.DISABLED_CIPHERSUITES);
+            }
+        }
+
+        @Override
+        public void close() throws Exception {
+            serverSocket.close();
+        }
+
+        public int getListeningPort() {
+            return serverSocket.getLocalPort();
+        }
+
+        public void run() throws IOException {
+            try (Socket clientSocket = serverSocket.accept()) {
+                try {
+                    byte[] bytes = clientSocket.getInputStream().readAllBytes();
+                    throw new RuntimeException("The expected SSLHandshakeException was not thrown.");
+                } catch (SSLHandshakeException exc) {
+                    if (!exc.getMessage().contains("no cipher suites in common")) {
+                        throw exc;
+                    } else {
+                        System.out.println("Success: The connection could not be negotiated (as expected.)");
+                    }
+                }
+            }
+        }
+    }
+}
-- 
GitLab