# HG changeset patch # User asmotrak # Date 1475001285 25200 # Node ID 240314163e5102bfee6ae74a4d3f72b1084c2805 # Parent d273dfe9a126d3bffe92072547fef2cd1361b0eb 8164591: sun/net/www/protocol/https/HttpsClient/ServerIdentityTest.java failed with SSLHandshakeException Reviewed-by: xuelei diff -r d273dfe9a126 -r 240314163e51 jdk/test/javax/net/ssl/templates/SSLTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/javax/net/ssl/templates/SSLTest.java Tue Sep 27 11:34:45 2016 -0700 @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2016, 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.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.security.KeyStore; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +/** + * Helper class for JSSE tests. + * + * Please run in othervm mode. SunJSSE does not support dynamic system + * properties, no way to re-use system properties in samevm/agentvm mode. + */ +public class SSLTest { + + public static final String TEST_SRC = System.getProperty("test.src", "."); + + /* + * Where do we find the keystores? + */ + public static final String PATH_TO_STORES = "../etc"; + public static final String KEY_STORE_FILE = "keystore"; + public static final String TRUST_STORE_FILE = "truststore"; + public static final String PASSWORD = "passphrase"; + + public static final int FREE_PORT = 0; + + // in seconds + public static final long CLIENT_SIGNAL_TIMEOUT = 30L; + public static final long SERVER_SIGNAL_TIMEOUT = 90L; + + // in millis + public static final int CLIENT_TIMEOUT = 15000; + public static final int SERVER_TIMEOUT = 30000; + + /* + * Should we run the client or server in a separate thread? + * Both sides can throw exceptions, but do you have a preference + * as to which side should be the main thread. + */ + private boolean separateServerThread = false; + + /* + * What's the server port? Use any free port by default + */ + private volatile int serverPort; + + private volatile Exception serverException; + private volatile Exception clientException; + + private Thread clientThread; + private Thread serverThread; + + private Peer serverPeer; + private Peer clientPeer; + + private Application serverApplication; + private Application clientApplication; + + private SSLContext context; + + /* + * Is the server ready to serve? + */ + private final CountDownLatch serverCondition = new CountDownLatch(1); + + /* + * Is the client ready to handshake? + */ + private final CountDownLatch clientCondition = new CountDownLatch(1); + + /* + * Public API. + */ + + public static interface Peer { + void run(SSLTest test) throws Exception; + } + + public static interface Application { + void run(SSLSocket socket, SSLTest test) throws Exception; + } + + public static void debug() { + debug("ssl"); + } + + public static void debug(String mode) { + System.setProperty("javax.net.debug", mode); + } + + public static void setup(String keyFilename, String trustFilename, + String password) { + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", password); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", password); + } + + public static void setup() throws Exception { + String keyFilename = TEST_SRC + "/" + PATH_TO_STORES + "/" + + KEY_STORE_FILE; + String trustFilename = TEST_SRC + "/" + PATH_TO_STORES + "/" + + TRUST_STORE_FILE; + + setup(keyFilename, trustFilename, PASSWORD); + } + + public static void print(String message, Throwable... errors) { + synchronized (System.out) { + System.out.println(message); + Arrays.stream(errors).forEach(e -> e.printStackTrace(System.out)); + } + } + + public static KeyStore loadJksKeyStore(String filename, String password) + throws Exception { + + return loadKeyStore(filename, password, "JKS"); + } + + public static KeyStore loadKeyStore(String filename, String password, + String type) throws Exception { + + KeyStore keystore = KeyStore.getInstance(type); + try (FileInputStream fis = new FileInputStream(filename)) { + keystore.load(fis, password.toCharArray()); + } + return keystore; + } + + public SSLTest setSeparateServerThread(boolean separateServerThread) { + this.separateServerThread = separateServerThread; + return this; + } + + public SSLTest setServerPort(int serverPort) { + this.serverPort = serverPort; + return this; + } + + public int getServerPort() { + return serverPort; + } + + public SSLTest setSSLContext(SSLContext context) { + this.context = context; + return this; + } + + public SSLContext getSSLContext() { + return context; + } + + public SSLServerSocketFactory getSSLServerSocketFactory() { + if (context != null) { + return context.getServerSocketFactory(); + } + + return (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + } + + public SSLSocketFactory getSSLSocketFactory() { + if (context != null) { + return context.getSocketFactory(); + } + + return (SSLSocketFactory) SSLSocketFactory.getDefault(); + } + + public void signalServerReady() { + serverCondition.countDown(); + } + + public boolean waitForClientSignal(long timeout, TimeUnit unit) + throws InterruptedException { + + return clientCondition.await(timeout, unit); + } + + public boolean waitForClientSignal() throws InterruptedException { + return waitForClientSignal(CLIENT_SIGNAL_TIMEOUT, TimeUnit.SECONDS); + } + + public void signalClientReady() { + clientCondition.countDown(); + } + + public boolean waitForServerSignal(long timeout, TimeUnit unit) + throws InterruptedException { + + return serverCondition.await(timeout, unit); + } + + public boolean waitForServerSignal() throws InterruptedException { + return waitForServerSignal(SERVER_SIGNAL_TIMEOUT, TimeUnit.SECONDS); + } + + public SSLTest setServerPeer(Peer serverPeer) { + this.serverPeer = serverPeer; + return this; + } + + public Peer getServerPeer() { + return serverPeer; + } + + public SSLTest setServerApplication(Application serverApplication) { + this.serverApplication = serverApplication; + return this; + } + + public Application getServerApplication() { + return serverApplication; + } + + public SSLTest setClientPeer(Peer clientPeer) { + this.clientPeer = clientPeer; + return this; + } + + public Peer getClientPeer() { + return clientPeer; + } + + public SSLTest setClientApplication(Application clientApplication) { + this.clientApplication = clientApplication; + return this; + } + + public Application getClientApplication() { + return clientApplication; + } + + public void runTest() throws Exception { + if (separateServerThread) { + startServer(true, this); + startClient(false, this); + serverThread.join(); + } else { + startClient(true, this); + startServer(false, this); + clientThread.join(); + } + + if (clientException != null || serverException != null) { + throw new RuntimeException("Test failed"); + } + } + + public SSLTest() { + serverPeer = (test) -> doServerSide(test); + clientPeer = (test) -> doClientSide(test); + serverApplication = (socket, test) -> runServerApplication(socket); + clientApplication = (socket, test) -> runClientApplication(socket); + } + + /* + * Private part. + */ + + + /* + * Define the server side of the test. + */ + private static void doServerSide(SSLTest test) throws Exception { + SSLServerSocket sslServerSocket; + + // kick start the server side service + SSLServerSocketFactory sslssf = test.getSSLServerSocketFactory(); + sslServerSocket = (SSLServerSocket)sslssf.createServerSocket(FREE_PORT); + + test.setServerPort(sslServerSocket.getLocalPort()); + print("Server is listening on port " + test.getServerPort()); + + // Signal the client, the server is ready to accept connection. + test.signalServerReady(); + + // Try to accept a connection in 30 seconds. + SSLSocket sslSocket; + try { + sslServerSocket.setSoTimeout(SERVER_TIMEOUT); + sslSocket = (SSLSocket) sslServerSocket.accept(); + print("Server accepted connection"); + } catch (SocketTimeoutException ste) { + sslServerSocket.close(); + + // Ignore the test case if no connection within 30 seconds. + print("No incoming client connection in 30 seconds. " + + "Ignore in server side.", ste); + return; + } + + // handle the connection + try { + // Is it the expected client connection? + // + // Naughty test cases or third party routines may try to + // connection to this server port unintentionally. In + // order to mitigate the impact of unexpected client + // connections and avoid intermittent failure, it should + // be checked that the accepted connection is really linked + // to the expected client. + boolean clientIsReady = test.waitForClientSignal(); + + if (clientIsReady) { + // Run the application in server side. + print("Run server application"); + test.getServerApplication().run(sslSocket, test); + } else { // Otherwise, ignore + // We don't actually care about plain socket connections + // for TLS communication testing generally. Just ignore + // the test if the accepted connection is not linked to + // the expected client or the client connection timeout + // in 30 seconds. + print("The client is not the expected one or timeout. " + + "Ignore in server side."); + } + } finally { + sslSocket.close(); + sslServerSocket.close(); + } + } + + /* + * Define the server side application of the test for the specified socket. + */ + private static void runServerApplication(SSLSocket socket) + throws Exception { + + // here comes the test logic + InputStream sslIS = socket.getInputStream(); + OutputStream sslOS = socket.getOutputStream(); + + sslIS.read(); + sslOS.write(85); + sslOS.flush(); + } + + /* + * Define the client side of the test. + */ + private static void doClientSide(SSLTest test) throws Exception { + + // Wait for server to get started. + // + // The server side takes care of the issue if the server cannot + // get started in 90 seconds. The client side would just ignore + // the test case if the serer is not ready. + boolean serverIsReady = test.waitForServerSignal(); + if (!serverIsReady) { + print("The server is not ready yet in 90 seconds. " + + "Ignore in client side."); + return; + } + + SSLSocketFactory sslsf = test.getSSLSocketFactory(); + try (SSLSocket sslSocket = (SSLSocket)sslsf.createSocket()) { + try { + sslSocket.connect( + new InetSocketAddress("localhost", + test.getServerPort()), CLIENT_TIMEOUT); + print("Client connected to server"); + } catch (IOException ioe) { + // The server side may be impacted by naughty test cases or + // third party routines, and cannot accept connections. + // + // Just ignore the test if the connection cannot be + // established. + print("Cannot make a connection in 15 seconds. " + + "Ignore in client side.", ioe); + return; + } + + // OK, here the client and server get connected. + + // Signal the server, the client is ready to communicate. + test.signalClientReady(); + + // There is still a chance in theory that the server thread may + // wait client-ready timeout and then quit. The chance should + // be really rare so we don't consider it until it becomes a + // real problem. + + // Run the application in client side. + print("Run client application"); + test.getClientApplication().run(sslSocket, test); + } + } + + /* + * Define the client side application of the test for the specified socket. + */ + private static void runClientApplication(SSLSocket socket) + throws Exception { + + InputStream sslIS = socket.getInputStream(); + OutputStream sslOS = socket.getOutputStream(); + + sslOS.write(280); + sslOS.flush(); + sslIS.read(); + } + + private void startServer(boolean newThread, SSLTest test) throws Exception { + if (newThread) { + serverThread = new Thread() { + @Override + public void run() { + try { + serverPeer.run(test); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + print("Server died ...", e); + serverException = e; + } + } + }; + serverThread.start(); + } else { + try { + serverPeer.run(test); + } catch (Exception e) { + print("Server failed ...", e); + serverException = e; + } + } + } + + private void startClient(boolean newThread, SSLTest test) throws Exception { + if (newThread) { + clientThread = new Thread() { + @Override + public void run() { + try { + clientPeer.run(test); + } catch (Exception e) { + /* + * Our client thread just died. + */ + print("Client died ...", e); + clientException = e; + } + } + }; + clientThread.start(); + } else { + try { + clientPeer.run(test); + } catch (Exception e) { + print("Client failed ...", e); + clientException = e; + } + } + } +} diff -r d273dfe9a126 -r 240314163e51 jdk/test/sun/net/www/protocol/https/HttpsClient/ServerIdentityTest.java --- a/jdk/test/sun/net/www/protocol/https/HttpsClient/ServerIdentityTest.java Wed Jul 05 22:16:18 2017 +0200 +++ b/jdk/test/sun/net/www/protocol/https/HttpsClient/ServerIdentityTest.java Tue Sep 27 11:34:45 2016 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2016, 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 @@ -26,7 +26,9 @@ * @bug 4328195 * @summary Need to include the alternate subject DN for certs, * https should check for this - * @run main/othervm ServerIdentityTest + * @library /javax/net/ssl/templates + * @run main/othervm ServerIdentityTest dnsstore + * @run main/othervm ServerIdentityTest ipstore * * SunJSSE does not support dynamic system properties, no way to re-use * system properties in samevm/agentvm mode. @@ -34,242 +36,71 @@ * @author Yingxian Wang */ -import java.io.*; -import java.net.*; -import javax.net.ssl.*; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; import java.security.KeyStore; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; public class ServerIdentityTest { - /* - * ============================================================= - * Set the various variables needed for the tests, then - * specify what tests to run on each side. - */ - - /* - * Should we run the client or server in a separate thread? - * Both sides can throw exceptions, but do you have a preference - * as to which side should be the main thread. - */ - static boolean separateServerThread = true; - - /* - * Where do we find the keystores? - */ - static String pathToStores = "./"; - static String[] keyStoreFiles = {"dnsstore", "ipstore"}; - static String[] trustStoreFiles = {"dnsstore", "ipstore"}; - static String passwd = "changeit"; - - /* - * Is the server ready to serve? - */ - boolean serverReady = false; - - /* - * Turn on SSL debugging? - */ - static boolean debug = false; - - /* - * If the client or server is doing some kind of object creation - * that the other side depends on, and that thread prematurely - * exits, you may experience a hang. The test harness will - * terminate all hung threads after its timeout has expired, - * currently 3 minutes by default, but you might try to be - * smart about it.... - */ - - /* - * Define the server side of the test. - * - * If the server prematurely exits, serverReady will be set to true - * to avoid infinite hangs. - */ - void doServerSide() throws Exception { - SSLServerSocketFactory sslssf = - context.getServerSocketFactory(); - SSLServerSocket sslServerSocket = - (SSLServerSocket) sslssf.createServerSocket(serverPort); - serverPort = sslServerSocket.getLocalPort(); - - /* - * Signal Client, we're ready for his connect. - */ - serverReady = true; - - SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); - OutputStream sslOS = sslSocket.getOutputStream(); - BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sslOS)); - bw.write("HTTP/1.1 200 OK\r\n\r\n\r\n"); - bw.flush(); - Thread.sleep(2000); - sslSocket.getSession().invalidate(); - sslSocket.close(); - } - - /* - * Define the client side of the test. - * - * If the server prematurely exits, serverReady will be set to true - * to avoid infinite hangs. - */ - void doClientSide() throws Exception { - /* - * Wait for server to get started. - */ - while (!serverReady) { - Thread.sleep(50); - } - String host = iphost? "127.0.0.1": "localhost"; - URL url = new URL("https://"+host+":"+serverPort+"/index.html"); - - HttpURLConnection urlc = (HttpURLConnection)url.openConnection(); - InputStream is = urlc.getInputStream(); - is.close(); - } - - /* - * ============================================================= - * The remainder is just support stuff - */ - - volatile int serverPort = 0; - - volatile Exception serverException = null; - volatile Exception clientException = null; + private static final String PASSWORD = "changeit"; public static void main(String[] args) throws Exception { - SSLSocketFactory reservedSFactory = - HttpsURLConnection.getDefaultSSLSocketFactory(); - try { - for (int i = 0; i < keyStoreFiles.length; i++) { - String keyFilename = - System.getProperty("test.src", ".") + "/" + pathToStores + - "/" + keyStoreFiles[i]; - String trustFilename = - System.getProperty("test.src", ".") + "/" + pathToStores + - "/" + trustStoreFiles[i]; + final String keystore = args[0]; + String keystoreFilename = SSLTest.TEST_SRC + "/" + keystore; - 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); - - if (debug) - System.setProperty("javax.net.debug", "all"); - SSLContext context = SSLContext.getInstance("SSL"); + SSLTest.setup(keystoreFilename, keystoreFilename, PASSWORD); - KeyManager[] kms = new KeyManager[1]; - KeyStore ks = KeyStore.getInstance("JKS"); - FileInputStream fis = new FileInputStream(keyFilename); - ks.load(fis, passwd.toCharArray()); - fis.close(); - KeyManager km = new MyKeyManager(ks, passwd.toCharArray()); - kms[0] = km; - context.init(kms, null, null); - HttpsURLConnection.setDefaultSSLSocketFactory( - context.getSocketFactory()); + SSLContext context = SSLContext.getInstance("SSL"); - /* - * Start the tests. - */ - System.out.println("Testing " + keyFilename); - new ServerIdentityTest(context, keyStoreFiles[i]); - } - } finally { - HttpsURLConnection.setDefaultSSLSocketFactory(reservedSFactory); - } - } - - Thread clientThread = null; - Thread serverThread = null; - - /* - * Primary constructor, used to drive remainder of the test. - * - * Fork off the other side, then do your work. - */ - SSLContext context; - boolean iphost = false; - ServerIdentityTest(SSLContext context, String keystore) - throws Exception { - this.context = context; - iphost = keystore.equals("ipstore"); - if (separateServerThread) { - startServer(true); - startClient(false); - } else { - startClient(true); - startServer(false); - } + KeyManager[] kms = new KeyManager[1]; + KeyStore ks = SSLTest.loadJksKeyStore(keystoreFilename, PASSWORD); + KeyManager km = new MyKeyManager(ks, PASSWORD.toCharArray()); + kms[0] = km; + context.init(kms, null, null); + HttpsURLConnection.setDefaultSSLSocketFactory( + context.getSocketFactory()); /* - * Wait for other side to close down. + * Start the test. */ - if (separateServerThread) { - serverThread.join(); - } else { - clientThread.join(); - } - - /* - * When we get here, the test is pretty much over. - * - * If the main thread excepted, that propagates back - * immediately. If the other thread threw an exception, we - * should report back. - */ - if (serverException != null) - throw serverException; - if (clientException != null) - throw clientException; - } + System.out.println("Testing " + keystore); - void startServer(boolean newThread) throws Exception { - if (newThread) { - serverThread = new Thread() { - public void run() { - try { - doServerSide(); - } catch (Exception e) { - e.printStackTrace(); - /* - * Our server thread just died. - * - * Release the client, if not active already... - */ - System.err.println("Server died..."); - serverReady = true; - serverException = e; - } + new SSLTest() + .setSSLContext(context) + .setServerApplication((socket, test) -> { + BufferedWriter bw = new BufferedWriter( + new OutputStreamWriter(socket.getOutputStream())); + bw.write("HTTP/1.1 200 OK\r\n\r\n\r\n"); + bw.flush(); + Thread.sleep(2000); + socket.getSession().invalidate(); + SSLTest.print("Server application is done"); + }) + .setClientPeer((test) -> { + boolean serverIsReady = test.waitForServerSignal(); + if (!serverIsReady) { + SSLTest.print( + "The server is not ready, ignore on client side."); + return; } - }; - serverThread.start(); - } else { - doServerSide(); - } - } + + // Signal the server, the client is ready to communicate. + test.signalClientReady(); - void startClient(boolean newThread) throws Exception { - if (newThread) { - clientThread = new Thread() { - public void run() { - try { - doClientSide(); - } catch (Exception e) { - /* - * Our client thread just died. - */ - System.err.println("Client died..."); - clientException = e; - } - } - }; - clientThread.start(); - } else { - doClientSide(); - } + String host = keystore.equals("ipstore") + ? "127.0.0.1" : "localhost"; + URL url = new URL("https://" + host + ":" + test.getServerPort() + + "/index.html"); + + ((HttpURLConnection) url.openConnection()) + .getInputStream().close(); + + SSLTest.print("Client is done"); + }).runTest(); } }