912 lines
30 KiB
Diff
912 lines
30 KiB
Diff
|
# HG changeset patch
|
||
|
# User rpatil
|
||
|
# Date 1474623897 -19800
|
||
|
# Fri Sep 23 15:14:57 2016 +0530
|
||
|
# Node ID fb617df8fbac42e962219e45cbd29b15b5ecdc63
|
||
|
# Parent d41592af9af3790fe5eee30ce686d85cff09c942
|
||
|
8144566, PR3352: Custom HostnameVerifier disables SNI extension
|
||
|
Reviewed-by: coffeys
|
||
|
|
||
|
diff --git a/src/share/classes/sun/security/ssl/SSLSocketImpl.java b/src/share/classes/sun/security/ssl/SSLSocketImpl.java
|
||
|
--- openjdk/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java
|
||
|
+++ openjdk/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java
|
||
|
@@ -1,5 +1,5 @@
|
||
|
/*
|
||
|
- * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
|
||
|
+ * Copyright (c) 1996, 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
|
||
|
@@ -220,6 +220,11 @@
|
||
|
Collections.<SNIServerName>emptyList();
|
||
|
Collection<SNIMatcher> sniMatchers =
|
||
|
Collections.<SNIMatcher>emptyList();
|
||
|
+ // Is the serverNames set to empty with SSLParameters.setServerNames()?
|
||
|
+ private boolean noSniExtension = false;
|
||
|
+
|
||
|
+ // Is the sniMatchers set to empty with SSLParameters.setSNIMatchers()?
|
||
|
+ private boolean noSniMatcher = false;
|
||
|
|
||
|
/*
|
||
|
* READ ME * READ ME * READ ME * READ ME * READ ME * READ ME *
|
||
|
@@ -666,6 +671,11 @@
|
||
|
}
|
||
|
|
||
|
super.connect(endpoint, timeout);
|
||
|
+
|
||
|
+ if (host == null || host.length() == 0) {
|
||
|
+ useImplicitHost(false);
|
||
|
+ }
|
||
|
+
|
||
|
doneConnect();
|
||
|
}
|
||
|
|
||
|
@@ -2158,41 +2168,61 @@
|
||
|
output.r.setVersion(protocolVersion);
|
||
|
}
|
||
|
|
||
|
+ //
|
||
|
+ // ONLY used by ClientHandshaker for the server hostname during handshaking
|
||
|
+ //
|
||
|
synchronized String getHost() {
|
||
|
// Note that the host may be null or empty for localhost.
|
||
|
if (host == null || host.length() == 0) {
|
||
|
- if (!trustNameService) {
|
||
|
- // If the local name service is not trustworthy, reverse host
|
||
|
- // name resolution should not be performed for endpoint
|
||
|
- // identification. Use the application original specified
|
||
|
- // hostname or IP address instead.
|
||
|
- host = getOriginalHostname(getInetAddress());
|
||
|
- } else {
|
||
|
- host = getInetAddress().getHostName();
|
||
|
- }
|
||
|
+ useImplicitHost(true);
|
||
|
}
|
||
|
|
||
|
return host;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
- * Get the original application specified hostname.
|
||
|
+ * Try to set and use the implicit specified hostname
|
||
|
*/
|
||
|
- private static String getOriginalHostname(InetAddress inetAddress) {
|
||
|
- /*
|
||
|
- * Get the original hostname via sun.misc.SharedSecrets.
|
||
|
- */
|
||
|
+ private synchronized void useImplicitHost(boolean noSniUpdate) {
|
||
|
+
|
||
|
+ // Note: If the local name service is not trustworthy, reverse
|
||
|
+ // host name resolution should not be performed for endpoint
|
||
|
+ // identification. Use the application original specified
|
||
|
+ // hostname or IP address instead.
|
||
|
+
|
||
|
+ // Get the original hostname via jdk.internal.misc.SharedSecrets
|
||
|
+ InetAddress inetAddress = getInetAddress();
|
||
|
+ if (inetAddress == null) { // not connected
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
JavaNetAccess jna = SharedSecrets.getJavaNetAccess();
|
||
|
String originalHostname = jna.getOriginalHostName(inetAddress);
|
||
|
+ if ((originalHostname != null) &&
|
||
|
+ (originalHostname.length() != 0)) {
|
||
|
|
||
|
- /*
|
||
|
- * If no application specified hostname, use the IP address.
|
||
|
- */
|
||
|
- if (originalHostname == null || originalHostname.length() == 0) {
|
||
|
- originalHostname = inetAddress.getHostAddress();
|
||
|
+ host = originalHostname;
|
||
|
+ if (!noSniUpdate && serverNames.isEmpty() && !noSniExtension) {
|
||
|
+ serverNames =
|
||
|
+ Utilities.addToSNIServerNameList(serverNames, host);
|
||
|
+
|
||
|
+ if (!roleIsServer &&
|
||
|
+ (handshaker != null) && !handshaker.started()) {
|
||
|
+ handshaker.setSNIServerNames(serverNames);
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ return;
|
||
|
}
|
||
|
|
||
|
- return originalHostname;
|
||
|
+ // No explicitly specified hostname, no server name indication.
|
||
|
+ if (!trustNameService) {
|
||
|
+ // The local name service is not trustworthy, use IP address.
|
||
|
+ host = inetAddress.getHostAddress();
|
||
|
+ } else {
|
||
|
+ // Use the underlying reverse host name resolution service.
|
||
|
+ host = getInetAddress().getHostName();
|
||
|
+ }
|
||
|
}
|
||
|
|
||
|
|
||
|
@@ -2205,6 +2235,10 @@
|
||
|
this.host = host;
|
||
|
this.serverNames =
|
||
|
Utilities.addToSNIServerNameList(this.serverNames, this.host);
|
||
|
+
|
||
|
+ if (!roleIsServer && (handshaker != null) && !handshaker.started()) {
|
||
|
+ handshaker.setSNIServerNames(serverNames);
|
||
|
+ }
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
@@ -2571,8 +2605,21 @@
|
||
|
// the super implementation does not handle the following parameters
|
||
|
params.setEndpointIdentificationAlgorithm(identificationProtocol);
|
||
|
params.setAlgorithmConstraints(algorithmConstraints);
|
||
|
- params.setSNIMatchers(sniMatchers);
|
||
|
- params.setServerNames(serverNames);
|
||
|
+
|
||
|
+ if (sniMatchers.isEmpty() && !noSniMatcher) {
|
||
|
+ // 'null' indicates none has been set
|
||
|
+ params.setSNIMatchers(null);
|
||
|
+ } else {
|
||
|
+ params.setSNIMatchers(sniMatchers);
|
||
|
+ }
|
||
|
+
|
||
|
+ if (serverNames.isEmpty() && !noSniExtension) {
|
||
|
+ // 'null' indicates none has been set
|
||
|
+ params.setServerNames(null);
|
||
|
+ } else {
|
||
|
+ params.setServerNames(serverNames);
|
||
|
+ }
|
||
|
+
|
||
|
params.setUseCipherSuitesOrder(preferLocalCipherSuites);
|
||
|
|
||
|
return params;
|
||
|
@@ -2592,11 +2639,13 @@
|
||
|
|
||
|
List<SNIServerName> sniNames = params.getServerNames();
|
||
|
if (sniNames != null) {
|
||
|
+ noSniExtension = sniNames.isEmpty();
|
||
|
serverNames = sniNames;
|
||
|
}
|
||
|
|
||
|
Collection<SNIMatcher> matchers = params.getSNIMatchers();
|
||
|
if (matchers != null) {
|
||
|
+ noSniMatcher = matchers.isEmpty();
|
||
|
sniMatchers = matchers;
|
||
|
}
|
||
|
|
||
|
diff --git a/test/javax/net/ssl/ServerName/BestEffortOnLazyConnected.java b/test/javax/net/ssl/ServerName/BestEffortOnLazyConnected.java
|
||
|
new file mode 100644
|
||
|
--- /dev/null
|
||
|
+++ openjdk/jdk/test/javax/net/ssl/ServerName/BestEffortOnLazyConnected.java
|
||
|
@@ -0,0 +1,337 @@
|
||
|
+/*
|
||
|
+ * 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.
|
||
|
+ */
|
||
|
+
|
||
|
+//
|
||
|
+// SunJSSE does not support dynamic system properties, no way to re-use
|
||
|
+// system properties in samevm/agentvm mode.
|
||
|
+//
|
||
|
+
|
||
|
+/**
|
||
|
+ * @test
|
||
|
+ * @bug 8144566
|
||
|
+ * @summary Custom HostnameVerifier disables SNI extension
|
||
|
+ * @run main/othervm BestEffortOnLazyConnected
|
||
|
+ */
|
||
|
+
|
||
|
+import java.io.*;
|
||
|
+import java.nio.*;
|
||
|
+import java.nio.channels.*;
|
||
|
+import java.util.*;
|
||
|
+import java.net.*;
|
||
|
+import javax.net.ssl.*;
|
||
|
+
|
||
|
+public class BestEffortOnLazyConnected {
|
||
|
+
|
||
|
+ /*
|
||
|
+ * =============================================================
|
||
|
+ * 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.
|
||
|
+ */
|
||
|
+ private static final boolean separateServerThread = true;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Where do we find the keystores?
|
||
|
+ */
|
||
|
+ private static final String pathToStores = "../../../../sun/security/ssl/etc";
|
||
|
+ private static final String keyStoreFile = "keystore";
|
||
|
+ private static final String trustStoreFile = "truststore";
|
||
|
+ private static final String passwd = "passphrase";
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Is the server ready to serve?
|
||
|
+ */
|
||
|
+ private static volatile boolean serverReady = false;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Turn on SSL debugging?
|
||
|
+ */
|
||
|
+ private static final boolean debug = false;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * the fully qualified domain name of localhost
|
||
|
+ */
|
||
|
+ private static String hostname = null;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * 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.
|
||
|
+ */
|
||
|
+ private void doServerSide() throws Exception {
|
||
|
+ SSLServerSocketFactory sslssf =
|
||
|
+ (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
|
||
|
+ try (SSLServerSocket sslServerSocket =
|
||
|
+ (SSLServerSocket) sslssf.createServerSocket(serverPort)) {
|
||
|
+
|
||
|
+ serverPort = sslServerSocket.getLocalPort();
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Signal Client, we're ready for his connect.
|
||
|
+ */
|
||
|
+ serverReady = true;
|
||
|
+
|
||
|
+ try (SSLSocket sslSocket = (SSLSocket)sslServerSocket.accept()) {
|
||
|
+ InputStream sslIS = sslSocket.getInputStream();
|
||
|
+ OutputStream sslOS = sslSocket.getOutputStream();
|
||
|
+
|
||
|
+ sslIS.read();
|
||
|
+ sslOS.write(85);
|
||
|
+ sslOS.flush();
|
||
|
+
|
||
|
+ ExtendedSSLSession session =
|
||
|
+ (ExtendedSSLSession)sslSocket.getSession();
|
||
|
+ if (session.getRequestedServerNames().isEmpty()) {
|
||
|
+ throw new Exception("No expected Server Name Indication");
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Define the client side of the test.
|
||
|
+ *
|
||
|
+ * If the server prematurely exits, serverReady will be set to true
|
||
|
+ * to avoid infinite hangs.
|
||
|
+ */
|
||
|
+ private void doClientSide() throws Exception {
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Wait for server to get started.
|
||
|
+ */
|
||
|
+ while (!serverReady) {
|
||
|
+ Thread.sleep(50);
|
||
|
+ }
|
||
|
+
|
||
|
+ SSLSocketFactory sslsf =
|
||
|
+ (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||
|
+
|
||
|
+ try (SSLSocket sslSocket = (SSLSocket)sslsf.createSocket()) {
|
||
|
+
|
||
|
+ sslSocket.connect(new InetSocketAddress(hostname, serverPort), 0);
|
||
|
+
|
||
|
+ InputStream sslIS = sslSocket.getInputStream();
|
||
|
+ OutputStream sslOS = sslSocket.getOutputStream();
|
||
|
+
|
||
|
+ sslOS.write(280);
|
||
|
+ sslOS.flush();
|
||
|
+ sslIS.read();
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+
|
||
|
+ /*
|
||
|
+ * =============================================================
|
||
|
+ * The remainder is just support stuff
|
||
|
+ */
|
||
|
+
|
||
|
+ // use any free port by default
|
||
|
+ private volatile int serverPort = 0;
|
||
|
+
|
||
|
+ private volatile Exception serverException = null;
|
||
|
+ private volatile Exception clientException = null;
|
||
|
+
|
||
|
+ public static void main(String[] args) throws Exception {
|
||
|
+ String keyFilename =
|
||
|
+ System.getProperty("test.src", ".") + "/" + pathToStores +
|
||
|
+ "/" + keyStoreFile;
|
||
|
+ String trustFilename =
|
||
|
+ System.getProperty("test.src", ".") + "/" + pathToStores +
|
||
|
+ "/" + trustStoreFile;
|
||
|
+
|
||
|
+ 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");
|
||
|
+ }
|
||
|
+
|
||
|
+ try {
|
||
|
+ hostname = InetAddress.getLocalHost().getCanonicalHostName();
|
||
|
+ } catch (UnknownHostException uhe) {
|
||
|
+ System.out.println(
|
||
|
+ "Ignore the test as the local hostname cannot be determined");
|
||
|
+
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ System.out.println(
|
||
|
+ "The fully qualified domain name of the local host is " +
|
||
|
+ hostname);
|
||
|
+ // Ignore the test if the hostname does not sound like a domain name.
|
||
|
+ if ((hostname == null) || hostname.isEmpty() ||
|
||
|
+ hostname.startsWith("localhost") ||
|
||
|
+ Character.isDigit(hostname.charAt(hostname.length() - 1))) {
|
||
|
+
|
||
|
+ System.out.println("Ignore the test as the local hostname " +
|
||
|
+ "cannot be determined as fully qualified domain name");
|
||
|
+
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Start the tests.
|
||
|
+ */
|
||
|
+ new BestEffortOnLazyConnected();
|
||
|
+ }
|
||
|
+
|
||
|
+ private Thread clientThread = null;
|
||
|
+ private Thread serverThread = null;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Primary constructor, used to drive remainder of the test.
|
||
|
+ *
|
||
|
+ * Fork off the other side, then do your work.
|
||
|
+ */
|
||
|
+ BestEffortOnLazyConnected() throws Exception {
|
||
|
+ try {
|
||
|
+ if (separateServerThread) {
|
||
|
+ startServer(true);
|
||
|
+ startClient(false);
|
||
|
+ } else {
|
||
|
+ startClient(true);
|
||
|
+ startServer(false);
|
||
|
+ }
|
||
|
+ } catch (Exception e) {
|
||
|
+ // swallow for now. Show later
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Wait for other side to close down.
|
||
|
+ */
|
||
|
+ if (separateServerThread) {
|
||
|
+ serverThread.join();
|
||
|
+ } else {
|
||
|
+ clientThread.join();
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * When we get here, the test is pretty much over.
|
||
|
+ * Which side threw the error?
|
||
|
+ */
|
||
|
+ Exception local;
|
||
|
+ Exception remote;
|
||
|
+ String whichRemote;
|
||
|
+
|
||
|
+ if (separateServerThread) {
|
||
|
+ remote = serverException;
|
||
|
+ local = clientException;
|
||
|
+ whichRemote = "server";
|
||
|
+ } else {
|
||
|
+ remote = clientException;
|
||
|
+ local = serverException;
|
||
|
+ whichRemote = "client";
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * If both failed, return the curthread's exception, but also
|
||
|
+ * print the remote side Exception
|
||
|
+ */
|
||
|
+ if ((local != null) && (remote != null)) {
|
||
|
+ System.out.println(whichRemote + " also threw:");
|
||
|
+ remote.printStackTrace();
|
||
|
+ System.out.println();
|
||
|
+ throw local;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (remote != null) {
|
||
|
+ throw remote;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (local != null) {
|
||
|
+ throw local;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ private void startServer(boolean newThread) throws Exception {
|
||
|
+ if (newThread) {
|
||
|
+ serverThread = new Thread() {
|
||
|
+ public void run() {
|
||
|
+ try {
|
||
|
+ doServerSide();
|
||
|
+ } catch (Exception e) {
|
||
|
+ /*
|
||
|
+ * Our server thread just died.
|
||
|
+ *
|
||
|
+ * Release the client, if not active already...
|
||
|
+ */
|
||
|
+ System.err.println("Server died...");
|
||
|
+ serverReady = true;
|
||
|
+ serverException = e;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ };
|
||
|
+ serverThread.start();
|
||
|
+ } else {
|
||
|
+ try {
|
||
|
+ doServerSide();
|
||
|
+ } catch (Exception e) {
|
||
|
+ serverException = e;
|
||
|
+ } finally {
|
||
|
+ serverReady = true;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ private 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 {
|
||
|
+ try {
|
||
|
+ doClientSide();
|
||
|
+ } catch (Exception e) {
|
||
|
+ clientException = e;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+}
|
||
|
diff --git a/test/sun/net/www/protocol/https/HttpsURLConnection/ImpactOnSNI.java b/test/sun/net/www/protocol/https/HttpsURLConnection/ImpactOnSNI.java
|
||
|
new file mode 100644
|
||
|
--- /dev/null
|
||
|
+++ openjdk/jdk/test/sun/net/www/protocol/https/HttpsURLConnection/ImpactOnSNI.java
|
||
|
@@ -0,0 +1,390 @@
|
||
|
+/*
|
||
|
+ * 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.
|
||
|
+ */
|
||
|
+
|
||
|
+//
|
||
|
+// SunJSSE does not support dynamic system properties, no way to re-use
|
||
|
+// system properties in samevm/agentvm mode.
|
||
|
+//
|
||
|
+
|
||
|
+/*
|
||
|
+ * @test
|
||
|
+ * @bug 8144566
|
||
|
+ * @summary Custom HostnameVerifier disables SNI extension
|
||
|
+ * @run main/othervm ImpactOnSNI
|
||
|
+ */
|
||
|
+
|
||
|
+import java.io.*;
|
||
|
+import java.net.*;
|
||
|
+import javax.net.ssl.*;
|
||
|
+
|
||
|
+public class ImpactOnSNI {
|
||
|
+
|
||
|
+ /*
|
||
|
+ * =============================================================
|
||
|
+ * 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.
|
||
|
+ */
|
||
|
+ private static final boolean separateServerThread = true;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Where do we find the keystores?
|
||
|
+ */
|
||
|
+ private static final String pathToStores =
|
||
|
+ "../../../../../../sun/security/ssl/etc";
|
||
|
+ private static final String keyStoreFile = "keystore";
|
||
|
+ private static final String trustStoreFile = "truststore";
|
||
|
+ private static final String passwd = "passphrase";
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Is the server ready to serve?
|
||
|
+ */
|
||
|
+ private static volatile boolean serverReady = false;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Is the connection ready to close?
|
||
|
+ */
|
||
|
+ private static volatile boolean closeReady = false;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Turn on SSL debugging?
|
||
|
+ */
|
||
|
+ private static final boolean debug = false;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Message posted
|
||
|
+ */
|
||
|
+ private static final String postMsg = "HTTP post on a https server";
|
||
|
+
|
||
|
+ /*
|
||
|
+ * the fully qualified domain name of localhost
|
||
|
+ */
|
||
|
+ private static String hostname = null;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * 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.
|
||
|
+ */
|
||
|
+ private void doServerSide() throws Exception {
|
||
|
+ SSLServerSocketFactory sslssf =
|
||
|
+ (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
|
||
|
+ try (SSLServerSocket sslServerSocket =
|
||
|
+ (SSLServerSocket)sslssf.createServerSocket(serverPort)) {
|
||
|
+
|
||
|
+ serverPort = sslServerSocket.getLocalPort();
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Signal Client, we're ready for his connect.
|
||
|
+ */
|
||
|
+ serverReady = true;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Accept connections
|
||
|
+ */
|
||
|
+ try (SSLSocket sslSocket = (SSLSocket)sslServerSocket.accept()) {
|
||
|
+ InputStream sslIS = sslSocket.getInputStream();
|
||
|
+ OutputStream sslOS = sslSocket.getOutputStream();
|
||
|
+ BufferedReader br =
|
||
|
+ new BufferedReader(new InputStreamReader(sslIS));
|
||
|
+ PrintStream ps = new PrintStream(sslOS);
|
||
|
+
|
||
|
+ // process HTTP POST request from client
|
||
|
+ System.out.println("status line: " + br.readLine());
|
||
|
+ String msg = null;
|
||
|
+ while ((msg = br.readLine()) != null && msg.length() > 0);
|
||
|
+
|
||
|
+ msg = br.readLine();
|
||
|
+ if (msg.equals(postMsg)) {
|
||
|
+ ps.println("HTTP/1.1 200 OK\n\n");
|
||
|
+ } else {
|
||
|
+ ps.println("HTTP/1.1 500 Not OK\n\n");
|
||
|
+ }
|
||
|
+ ps.flush();
|
||
|
+
|
||
|
+ ExtendedSSLSession session =
|
||
|
+ (ExtendedSSLSession)sslSocket.getSession();
|
||
|
+ if (session.getRequestedServerNames().isEmpty()) {
|
||
|
+ throw new Exception("No expected Server Name Indication");
|
||
|
+ }
|
||
|
+
|
||
|
+ // close the socket
|
||
|
+ while (!closeReady) {
|
||
|
+ Thread.sleep(50);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Define the client side of the test.
|
||
|
+ *
|
||
|
+ * If the server prematurely exits, serverReady will be set to true
|
||
|
+ * to avoid infinite hangs.
|
||
|
+ */
|
||
|
+ private void doClientSide() throws Exception {
|
||
|
+ /*
|
||
|
+ * Wait for server to get started.
|
||
|
+ */
|
||
|
+ while (!serverReady) {
|
||
|
+ Thread.sleep(50);
|
||
|
+ }
|
||
|
+
|
||
|
+ // Send HTTP POST request to server
|
||
|
+ URL url = new URL("https://" + hostname + ":" + serverPort);
|
||
|
+
|
||
|
+ HttpsURLConnection.setDefaultHostnameVerifier(new NameVerifier());
|
||
|
+ HttpsURLConnection http = (HttpsURLConnection)url.openConnection();
|
||
|
+ http.setDoOutput(true);
|
||
|
+
|
||
|
+ http.setRequestMethod("POST");
|
||
|
+ PrintStream ps = new PrintStream(http.getOutputStream());
|
||
|
+ try {
|
||
|
+ ps.println(postMsg);
|
||
|
+ ps.flush();
|
||
|
+ if (http.getResponseCode() != 200) {
|
||
|
+ throw new RuntimeException("test Failed");
|
||
|
+ }
|
||
|
+ } finally {
|
||
|
+ ps.close();
|
||
|
+ http.disconnect();
|
||
|
+ closeReady = true;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ private static class NameVerifier implements HostnameVerifier {
|
||
|
+ public boolean verify(String hostname, SSLSession session) {
|
||
|
+ return true;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * =============================================================
|
||
|
+ * The remainder is just support stuff
|
||
|
+ */
|
||
|
+
|
||
|
+ // use any free port by default
|
||
|
+ private volatile int serverPort = 0;
|
||
|
+
|
||
|
+ private volatile Exception serverException = null;
|
||
|
+ private volatile Exception clientException = null;
|
||
|
+
|
||
|
+ public static void main(String[] args) throws Exception {
|
||
|
+ String keyFilename =
|
||
|
+ System.getProperty("test.src", "./") + "/" + pathToStores +
|
||
|
+ "/" + keyStoreFile;
|
||
|
+ String trustFilename =
|
||
|
+ System.getProperty("test.src", "./") + "/" + pathToStores +
|
||
|
+ "/" + trustStoreFile;
|
||
|
+
|
||
|
+ 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");
|
||
|
+ }
|
||
|
+
|
||
|
+ try {
|
||
|
+ hostname = InetAddress.getLocalHost().getCanonicalHostName();
|
||
|
+ } catch (UnknownHostException uhe) {
|
||
|
+ System.out.println(
|
||
|
+ "Ignore the test as the local hostname cannot be determined");
|
||
|
+
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ System.out.println(
|
||
|
+ "The fully qualified domain name of the local host is " +
|
||
|
+ hostname);
|
||
|
+ // Ignore the test if the hostname does not sound like a domain name.
|
||
|
+ if ((hostname == null) || hostname.isEmpty() ||
|
||
|
+ hostname.startsWith("localhost") ||
|
||
|
+ Character.isDigit(hostname.charAt(hostname.length() - 1))) {
|
||
|
+
|
||
|
+ System.out.println("Ignore the test as the local hostname " +
|
||
|
+ "cannot be determined as fully qualified domain name");
|
||
|
+
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Start the tests.
|
||
|
+ */
|
||
|
+ new ImpactOnSNI();
|
||
|
+ }
|
||
|
+
|
||
|
+ private Thread clientThread = null;
|
||
|
+ private Thread serverThread = null;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Primary constructor, used to drive remainder of the test.
|
||
|
+ *
|
||
|
+ * Fork off the other side, then do your work.
|
||
|
+ */
|
||
|
+ ImpactOnSNI() throws Exception {
|
||
|
+ Exception startException = null;
|
||
|
+ try {
|
||
|
+ if (separateServerThread) {
|
||
|
+ startServer(true);
|
||
|
+ startClient(false);
|
||
|
+ } else {
|
||
|
+ startClient(true);
|
||
|
+ startServer(false);
|
||
|
+ }
|
||
|
+ } catch (Exception e) {
|
||
|
+ startException = e;
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Wait for other side to close down.
|
||
|
+ */
|
||
|
+ if (separateServerThread) {
|
||
|
+ if (serverThread != null) {
|
||
|
+ serverThread.join();
|
||
|
+ }
|
||
|
+ } else {
|
||
|
+ if (clientThread != null) {
|
||
|
+ clientThread.join();
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * When we get here, the test is pretty much over.
|
||
|
+ * Which side threw the error?
|
||
|
+ */
|
||
|
+ Exception local;
|
||
|
+ Exception remote;
|
||
|
+
|
||
|
+ if (separateServerThread) {
|
||
|
+ remote = serverException;
|
||
|
+ local = clientException;
|
||
|
+ } else {
|
||
|
+ remote = clientException;
|
||
|
+ local = serverException;
|
||
|
+ }
|
||
|
+
|
||
|
+ Exception exception = null;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Check various exception conditions.
|
||
|
+ */
|
||
|
+ if ((local != null) && (remote != null)) {
|
||
|
+ // If both failed, return the curthread's exception.
|
||
|
+ local.initCause(remote);
|
||
|
+ exception = local;
|
||
|
+ } else if (local != null) {
|
||
|
+ exception = local;
|
||
|
+ } else if (remote != null) {
|
||
|
+ exception = remote;
|
||
|
+ } else if (startException != null) {
|
||
|
+ exception = startException;
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * If there was an exception *AND* a startException,
|
||
|
+ * output it.
|
||
|
+ */
|
||
|
+ if (exception != null) {
|
||
|
+ if (exception != startException && startException != null) {
|
||
|
+ exception.addSuppressed(startException);
|
||
|
+ }
|
||
|
+ throw exception;
|
||
|
+ }
|
||
|
+
|
||
|
+ // Fall-through: no exception to throw!
|
||
|
+ }
|
||
|
+
|
||
|
+ private void startServer(boolean newThread) throws Exception {
|
||
|
+ if (newThread) {
|
||
|
+ serverThread = new Thread() {
|
||
|
+ @Override
|
||
|
+ public void run() {
|
||
|
+ try {
|
||
|
+ doServerSide();
|
||
|
+ } catch (Exception e) {
|
||
|
+ /*
|
||
|
+ * Our server thread just died.
|
||
|
+ *
|
||
|
+ * Release the client, if not active already...
|
||
|
+ */
|
||
|
+ System.err.println("Server died...");
|
||
|
+ serverReady = true;
|
||
|
+ serverException = e;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ };
|
||
|
+ serverThread.start();
|
||
|
+ } else {
|
||
|
+ try {
|
||
|
+ doServerSide();
|
||
|
+ } catch (Exception e) {
|
||
|
+ serverException = e;
|
||
|
+ } finally {
|
||
|
+ serverReady = true;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ private void startClient(boolean newThread) throws Exception {
|
||
|
+ if (newThread) {
|
||
|
+ clientThread = new Thread() {
|
||
|
+ @Override
|
||
|
+ public void run() {
|
||
|
+ try {
|
||
|
+ doClientSide();
|
||
|
+ } catch (Exception e) {
|
||
|
+ /*
|
||
|
+ * Our client thread just died.
|
||
|
+ */
|
||
|
+ System.err.println("Client died...");
|
||
|
+ clientException = e;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ };
|
||
|
+ clientThread.start();
|
||
|
+ } else {
|
||
|
+ try {
|
||
|
+ doClientSide();
|
||
|
+ } catch (Exception e) {
|
||
|
+ clientException = e;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+}
|