/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.harmony.luni.tests.internal.net.www.protocol.https; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.Authenticator; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; import java.security.KeyStore; import java.security.cert.Certificate; import java.util.Arrays; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import junit.framework.TestCase; import libcore.java.security.TestKeyStore; import libcore.javax.net.ssl.TestTrustManager; /** * Implementation independent test for HttpsURLConnection. * The test needs certstore file placed in system classpath * and named as "key_store." + the type of the * default KeyStore installed in the system in lower case. * <br> * For example: if default KeyStore type in the system is BKS * (i.e. java.security file sets up the property keystore.type=BKS), * thus classpath should point to the directory with "key_store.bks" * file. * <br> * This certstore file should contain self-signed certificate * generated by keytool utility in a usual way. * <br> * The password to the certstore should be "password" (without quotes). */ public class HttpsURLConnectionTest extends TestCase { // the password to the store private static final String KS_PASSWORD = "password"; // turn on/off logging private static final boolean DO_LOG = false; // read/connection timeout value private static final int TIMEOUT = 5000; // OK response code private static final int OK_CODE = 200; // Not Found response code private static final int NOT_FOUND_CODE = 404; // Proxy authentication required response code private static final int AUTHENTICATION_REQUIRED_CODE = 407; private static File store; static { try { store = File.createTempFile("key_store", "bks"); } catch (Exception e) { // ignore } } /** * Checks that HttpsURLConnection's default SSLSocketFactory is operable. */ public void testGetDefaultSSLSocketFactory() throws Exception { // set up the properties defining the default values needed by SSL stuff setUpStoreProperties(); SSLSocketFactory defaultSSLSF = HttpsURLConnection.getDefaultSSLSocketFactory(); ServerSocket ss = new ServerSocket(0); Socket s = defaultSSLSF.createSocket("localhost", ss.getLocalPort()); ss.accept(); s.close(); ss.close(); } public void testHttpsConnection() throws Throwable { // set up the properties defining the default values needed by SSL stuff setUpStoreProperties(); // create the SSL server socket acting as a server SSLContext ctx = getContext(); ServerSocket ss = ctx.getServerSocketFactory().createServerSocket(0); // create the HostnameVerifier to check hostname verification TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create url connection to be tested URL url = new URL("https://localhost:" + ss.getLocalPort()); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setSSLSocketFactory(ctx.getSocketFactory()); // perform the interaction between the peers SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); // check the connection state checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } /** * Tests the behaviour of HTTPS connection in case of unavailability * of requested resource. */ public void testHttpsConnection_Not_Found_Response() throws Throwable { // set up the properties defining the default values needed by SSL stuff setUpStoreProperties(); // create the SSL server socket acting as a server SSLContext ctx = getContext(); ServerSocket ss = ctx.getServerSocketFactory().createServerSocket(0); // create the HostnameVerifier to check hostname verification TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create url connection to be tested URL url = new URL("https://localhost:" + ss.getLocalPort()); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setSSLSocketFactory(ctx.getSocketFactory()); try { doInteraction(connection, ss, NOT_FOUND_CODE); fail("Expected exception was not thrown."); } catch (FileNotFoundException e) { if (DO_LOG) { System.out.println("Expected exception was thrown: " + e.getMessage()); e.printStackTrace(); } } // should silently exit connection.connect(); } /** * Tests possibility to set up the default SSLSocketFactory * to be used by HttpsURLConnection. */ public void testSetDefaultSSLSocketFactory() throws Throwable { // create the SSLServerSocket which will be used by server side SSLContext ctx = getContext(); SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(0); SSLSocketFactory socketFactory = (SSLSocketFactory) ctx.getSocketFactory(); // set up the factory as default HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); // check the result assertSame("Default SSLSocketFactory differs from expected", socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory()); // create the HostnameVerifier to check hostname verification TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create HttpsURLConnection to be tested URL url = new URL("https://localhost:" + ss.getLocalPort()); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); TestHostnameVerifier hnv_late = new TestHostnameVerifier(); // late initialization: should not be used for created connection HttpsURLConnection.setDefaultHostnameVerifier(hnv_late); // perform the interaction between the peers SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); // check the connection state checkConnectionStateParameters(connection, peerSocket); // check the verification process assertTrue("Hostname verification was not done", hnv.verified); assertFalse("Hostname verification should not be done by this verifier", hnv_late.verified); // check the used SSLSocketFactory assertSame("Default SSLSocketFactory should be used", HttpsURLConnection.getDefaultSSLSocketFactory(), connection.getSSLSocketFactory()); // should silently exit connection.connect(); } /** * Tests possibility to set up the SSLSocketFactory * to be used by HttpsURLConnection. */ public void testSetSSLSocketFactory() throws Throwable { // create the SSLServerSocket which will be used by server side SSLContext ctx = getContext(); SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(0); // create the HostnameVerifier to check hostname verification TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create HttpsURLConnection to be tested URL url = new URL("https://localhost:" + ss.getLocalPort()); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); SSLSocketFactory socketFactory = (SSLSocketFactory) ctx.getSocketFactory(); connection.setSSLSocketFactory(socketFactory); TestHostnameVerifier hnv_late = new TestHostnameVerifier(); // late initialization: should not be used for created connection HttpsURLConnection.setDefaultHostnameVerifier(hnv_late); // perform the interaction between the peers SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); // check the connection state checkConnectionStateParameters(connection, peerSocket); // check the verification process assertTrue("Hostname verification was not done", hnv.verified); assertFalse("Hostname verification should not be done by this verifier", hnv_late.verified); // check the used SSLSocketFactory assertNotSame("Default SSLSocketFactory should not be used", HttpsURLConnection.getDefaultSSLSocketFactory(), connection.getSSLSocketFactory()); assertSame("Result differs from expected", socketFactory, connection.getSSLSocketFactory()); // should silently exit connection.connect(); } /** * Tests the behaviour of HttpsURLConnection in case of retrieving * of the connection state parameters before connection has been made. */ public void testUnconnectedStateParameters() throws Throwable { // create HttpsURLConnection to be tested URL url = new URL("https://localhost:55555"); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); try { connection.getCipherSuite(); fail("Expected IllegalStateException was not thrown"); } catch (IllegalStateException e) {} try { connection.getPeerPrincipal(); fail("Expected IllegalStateException was not thrown"); } catch (IllegalStateException e) {} try { connection.getLocalPrincipal(); fail("Expected IllegalStateException was not thrown"); } catch (IllegalStateException e) {} try { connection.getServerCertificates(); fail("Expected IllegalStateException was not thrown"); } catch (IllegalStateException e) {} try { connection.getLocalCertificates(); fail("Expected IllegalStateException was not thrown"); } catch (IllegalStateException e) {} } /** * Tests if setHostnameVerifier() method replaces default verifier. */ public void testSetHostnameVerifier() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); // create the SSLServerSocket which will be used by server side SSLServerSocket ss = (SSLServerSocket) getContext().getServerSocketFactory().createServerSocket(0); // create the HostnameVerifier to check that Hostname verification // is done TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create HttpsURLConnection to be tested URL url = new URL("https://localhost:" + ss.getLocalPort()); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setSSLSocketFactory(getContext().getSocketFactory()); TestHostnameVerifier hnv_late = new TestHostnameVerifier(); // replace default verifier connection.setHostnameVerifier(hnv_late); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); assertTrue("Hostname verification was not done", hnv_late.verified); assertFalse("Hostname verification should not be done by this verifier", hnv.verified); checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } /** * Tests the behaviour in case of sending the data to the server. */ public void test_doOutput() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); // create the SSLServerSocket which will be used by server side SSLServerSocket ss = (SSLServerSocket) getContext().getServerSocketFactory().createServerSocket(0); // create the HostnameVerifier to check that Hostname verification // is done TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create HttpsURLConnection to be tested URL url = new URL("https://localhost:" + ss.getLocalPort()); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setSSLSocketFactory(getContext().getSocketFactory()); connection.setDoOutput(true); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } /** * Tests HTTPS connection process made through the proxy server. */ public void testProxyConnection() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); // create the SSLServerSocket which will be used by server side ServerSocket ss = new ServerSocket(0); // create the HostnameVerifier to check that Hostname verification // is done TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create HttpsURLConnection to be tested URL url = new URL("https://requested.host:55556/requested.data"); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort()))); connection.setSSLSocketFactory(getContext().getSocketFactory()); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } /** * Tests HTTPS connection process made through the proxy server. * Proxy server needs authentication. */ public void testProxyAuthConnection() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); // create the SSLServerSocket which will be used by server side ServerSocket ss = new ServerSocket(0); // create the HostnameVerifier to check that Hostname verification // is done TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); Authenticator.setDefault(new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("user", "password" .toCharArray()); } }); // create HttpsURLConnection to be tested URL url = new URL("https://requested.host:55555/requested.data"); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort()))); connection.setSSLSocketFactory(getContext().getSocketFactory()); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } /** * Tests HTTPS connection process made through the proxy server. * 2 HTTPS connections are opened for one URL. For the first time * the connection is opened through one proxy, * for the second time through another. */ public void testConsequentProxyConnection() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); // create the SSLServerSocket which will be used by server side ServerSocket ss = new ServerSocket(0); // create the HostnameVerifier to check that Hostname verification // is done TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create HttpsURLConnection to be tested URL url = new URL("https://requested.host:55555/requested.data"); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort()))); connection.setSSLSocketFactory(getContext().getSocketFactory()); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); checkConnectionStateParameters(connection, peerSocket); // create another SSLServerSocket which will be used by server side ss = new ServerSocket(0); connection = (HttpsURLConnection) url.openConnection(new Proxy( Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort()))); connection.setSSLSocketFactory(getContext().getSocketFactory()); // perform the interaction between the peers and check the results peerSocket = (SSLSocket) doInteraction(connection, ss); checkConnectionStateParameters(connection, peerSocket); } /** * Tests HTTPS connection process made through the proxy server. * Proxy server needs authentication. * Client sends data to the server. */ public void testProxyAuthConnection_doOutput() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); // create the SSLServerSocket which will be used by server side ServerSocket ss = new ServerSocket(0); // create the HostnameVerifier to check that Hostname verification // is done TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); Authenticator.setDefault(new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("user", "password" .toCharArray()); } }); // create HttpsURLConnection to be tested URL url = new URL("https://requested.host:55554/requested.data"); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort()))); connection.setSSLSocketFactory(getContext().getSocketFactory()); connection.setDoOutput(true); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss, OK_CODE, true); checkConnectionStateParameters(connection, peerSocket); } /** * Tests HTTPS connection process made through the proxy server. * Proxy server needs authentication but client fails to authenticate * (Authenticator was not set up in the system). */ public void testProxyAuthConnectionFailed() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); // create the SSLServerSocket which will be used by server side ServerSocket ss = new ServerSocket(0); // create the HostnameVerifier to check that Hostname verification // is done TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create HttpsURLConnection to be tested URL url = new URL("https://requested.host:55555/requested.data"); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort()))); connection.setSSLSocketFactory(getContext().getSocketFactory()); // perform the interaction between the peers and check the results try { doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE, true); } catch (IOException e) { // SSL Tunnelling failed if (DO_LOG) { System.out.println("Got expected IOException: " + e.getMessage()); } } } /** * Tests the behaviour of HTTPS connection in case of unavailability * of requested resource. */ public void testProxyConnection_Not_Found_Response() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); // create the SSLServerSocket which will be used by server side ServerSocket ss = new ServerSocket(0); // create the HostnameVerifier to check that Hostname verification // is done TestHostnameVerifier hnv = new TestHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); // create HttpsURLConnection to be tested URL url = new URL("https://localhost:" + ss.getLocalPort()); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort()))); connection.setSSLSocketFactory(getContext().getSocketFactory()); try { doInteraction(connection, ss, NOT_FOUND_CODE); // NOT FOUND fail("Expected exception was not thrown."); } catch (FileNotFoundException e) { if (DO_LOG) { System.out.println("Expected exception was thrown: " + e.getMessage()); } } } /** * Log the name of the test case to be executed. */ public void setUp() throws Exception { super.setUp(); if (DO_LOG) { System.out.println(); System.out.println("------------------------"); System.out.println("------ " + getName()); System.out.println("------------------------"); } if (store != null) { String ksFileName = ("org/apache/harmony/luni/tests/key_store." + KeyStore.getDefaultType().toLowerCase()); InputStream in = getClass().getClassLoader().getResourceAsStream(ksFileName); FileOutputStream out = new FileOutputStream(store); BufferedInputStream bufIn = new BufferedInputStream(in, 8192); while (bufIn.available() > 0) { byte[] buf = new byte[128]; int read = bufIn.read(buf); out.write(buf, 0, read); } bufIn.close(); out.close(); } else { fail("couldn't set up key store"); } } public void tearDown() { if (store != null) { store.delete(); } } /** * Checks the HttpsURLConnection getter's values and compares * them with actual corresponding values of remote peer. */ public static void checkConnectionStateParameters( HttpsURLConnection clientConnection, SSLSocket serverPeer) throws Exception { SSLSession session = serverPeer.getSession(); assertEquals(session.getCipherSuite(), clientConnection.getCipherSuite()); assertEquals(session.getLocalPrincipal(), clientConnection.getPeerPrincipal()); assertEquals(session.getPeerPrincipal(), clientConnection.getLocalPrincipal()); Certificate[] serverCertificates = clientConnection.getServerCertificates(); Certificate[] localCertificates = session.getLocalCertificates(); assertTrue("Server certificates differ from expected", Arrays.equals(serverCertificates, localCertificates)); localCertificates = clientConnection.getLocalCertificates(); serverCertificates = session.getPeerCertificates(); assertTrue("Local certificates differ from expected", Arrays.equals(serverCertificates, localCertificates)); } /** * Returns the file name of the key/trust store. The key store file * (named as "key_store." + extension equals to the default KeyStore * type installed in the system in lower case) is searched in classpath. * @throws junit.framework.AssertionFailedError if property was not set * or file does not exist. */ private static String getKeyStoreFileName() { return store.getAbsolutePath(); } /** * Builds and returns the context used for secure socket creation. */ private static SSLContext getContext() throws Exception { String type = KeyStore.getDefaultType(); String keyStore = getKeyStoreFileName(); File keyStoreFile = new File(keyStore); FileInputStream fis = new FileInputStream(keyStoreFile); KeyStore ks = KeyStore.getInstance(type); ks.load(fis, KS_PASSWORD.toCharArray()); fis.close(); if (DO_LOG && false) { TestKeyStore.dump("HttpsURLConnection.getContext", ks, KS_PASSWORD.toCharArray()); } String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm); kmf.init(ks, KS_PASSWORD.toCharArray()); KeyManager[] keyManagers = kmf.getKeyManagers(); String tmfAlgorthm = TrustManagerFactory.getDefaultAlgorithm(); TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorthm); tmf.init(ks); TrustManager[] trustManagers = tmf.getTrustManagers(); if (DO_LOG) { trustManagers = TestTrustManager.wrap(trustManagers); } SSLContext ctx = SSLContext.getInstance("TLSv1"); ctx.init(keyManagers, trustManagers, null); return ctx; } /** * Sets up the properties pointing to the key store and trust store * and used as default values by JSSE staff. This is needed to test * HTTPS behaviour in the case of default SSL Socket Factories. */ private static void setUpStoreProperties() throws Exception { String type = KeyStore.getDefaultType(); System.setProperty("javax.net.ssl.keyStoreType", type); System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName()); System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD); System.setProperty("javax.net.ssl.trustStoreType", type); System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName()); System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD); } /** * Performs interaction between client's HttpsURLConnection and * servers side (ServerSocket). */ public static Socket doInteraction(final HttpsURLConnection clientConnection, final ServerSocket serverSocket) throws Throwable { return doInteraction(clientConnection, serverSocket, OK_CODE, false); } /** * Performs interaction between client's HttpsURLConnection and * servers side (ServerSocket). Server will response with specified * response code. */ public static Socket doInteraction(final HttpsURLConnection clientConnection, final ServerSocket serverSocket, final int responseCode) throws Throwable { return doInteraction(clientConnection, serverSocket, responseCode, false); } /** * Performs interaction between client's HttpsURLConnection and * servers side (ServerSocket). Server will response with specified * response code. * @param doAuthentication specifies * if the server needs client authentication. */ public static Socket doInteraction(final HttpsURLConnection clientConnection, final ServerSocket serverSocket, final int responseCode, final boolean doAuthentication) throws Throwable { // set up the connection clientConnection.setDoInput(true); clientConnection.setConnectTimeout(TIMEOUT); clientConnection.setReadTimeout(TIMEOUT); ServerWork server = new ServerWork(serverSocket, responseCode, doAuthentication); ClientConnectionWork client = new ClientConnectionWork(clientConnection); ExecutorService executorService = Executors.newFixedThreadPool(2); try { Future<Void> serverFuture = executorService.submit(server); Future<Void> clientFuture = executorService.submit(client); Throwable t = null; try { serverFuture.get(30, TimeUnit.SECONDS); } catch (ExecutionException e) { t = e.getCause(); } try { clientFuture.get(30, TimeUnit.SECONDS); } catch (ExecutionException e) { // two problems? log the first before overwriting if (t != null) { t.printStackTrace(); } t = e.getCause(); } if (t != null) { throw t; } } catch (ExecutionException e) { throw e.getCause(); } finally { executorService.shutdown(); } return server.peerSocket; } /** * The host name verifier used in test. */ static class TestHostnameVerifier implements HostnameVerifier { boolean verified = false; public boolean verify(String hostname, SSLSession session) { if (DO_LOG) { System.out.println("***> verification " + hostname + " " + session.getPeerHost()); } verified = true; return true; } } /** * The base class for mock Client and Server. */ static class Work { /** * The header of OK HTTP response. */ static final String responseHead = "HTTP/1.1 200 OK\r\n"; /** * The response message to be sent to the proxy CONNECT request. */ static final String proxyResponse = responseHead + "\r\n"; /** * The content of the response to be sent during HTTPS session. */ static final String httpsResponseContent = "<HTML>\n" + "<HEAD><TITLE>HTTPS Response Content</TITLE></HEAD>\n" + "</HTML>"; /** * The tail of the response to be sent during HTTPS session. */ static final String httpsResponseTail = "Content-type: text/html\r\n" + "Content-length: " + httpsResponseContent.length() + "\r\n" + "\r\n" + httpsResponseContent; /** * The response requiring client's proxy authentication. */ static final String respAuthenticationRequired = "HTTP/1.0 407 Proxy authentication required\r\n" + "Proxy-authenticate: Basic realm=\"localhost\"\r\n" + "\r\n"; /** * The data to be posted by client to the server. */ static final String clientsData = "_.-^ Client's Data ^-._"; /** * The print stream used for debug log. * If it is null debug info will not be printed. */ private PrintStream out = System.out; /** * Prints log message. */ public synchronized void log(String message) { if (DO_LOG && (out != null)) { out.println("[" + this + "]: " + message); } } } /** * The class used for server side works. */ static class ServerWork extends Work implements Callable<Void> { // the server socket used for connection private final ServerSocket serverSocket; // indicates if the server acts as proxy server private final boolean actAsProxy; // indicates if the server needs proxy authentication private final boolean needProxyAuthentication; // response code to be send to the client peer private final int responseCode; // the socket connected with client peer private Socket peerSocket; /** * Creates the thread acting as a server side. * @param serverSocket the server socket to be used during connection * @param responseCode the response code to be sent to the client * @param needProxyAuthentication * indicates if the server needs proxy authentication */ public ServerWork(ServerSocket serverSocket, int responseCode, boolean needProxyAuthentication) { this.serverSocket = serverSocket; this.responseCode = responseCode; this.needProxyAuthentication = needProxyAuthentication; // will act as a proxy server if the specified server socket // is not a secure server socket this.actAsProxy = !(serverSocket instanceof SSLServerSocket); if (!actAsProxy) { // demand client to send its certificate ((SSLServerSocket) serverSocket).setNeedClientAuth(true); } } /** * Closes the connection. */ public void closeSocket(Socket socket) { if (socket == null) { return; } try { socket.getInputStream().close(); } catch (IOException e) {} try { socket.getOutputStream().close(); } catch (IOException e) {} try { socket.close(); } catch (IOException e) {} } /** * Performs the actual server work. * If some exception occurs during the work it will be * stored in the <code>thrown</code> field. */ public Void call() throws Exception { // the buffer used for reading the messages byte[] buff = new byte[2048]; // the number of bytes read into the buffer try { // configure the server socket to avoid blocking serverSocket.setSoTimeout(TIMEOUT); // accept client connection peerSocket = serverSocket.accept(); // configure the client connection to avoid blocking peerSocket.setSoTimeout(TIMEOUT); log("Client connection ACCEPTED"); InputStream is = peerSocket.getInputStream(); OutputStream os = peerSocket.getOutputStream(); int num = is.read(buff); if (num == -1) { log("Unexpected EOF"); return null; } String message = new String(buff, 0, num); log("Got request:\n" + message); log("------------------"); if (!actAsProxy) { // Act as Server (not Proxy) side if (message.startsWith("POST")) { // client connection sent some data log("try to read client data"); String data = message.substring(message.indexOf("\r\n\r\n")+4); log("client's data: '" + data + "'"); // check the received data assertEquals(clientsData, data); } } else { if (needProxyAuthentication) { // Do proxy work log("Authentication required..."); // send Authentication Request os.write(respAuthenticationRequired.getBytes()); // read request num = is.read(buff); if (num == -1) { // this connection was closed, // do clean up and create new one: closeSocket(peerSocket); peerSocket = serverSocket.accept(); peerSocket.setSoTimeout(TIMEOUT); log("New client connection ACCEPTED"); is = peerSocket.getInputStream(); os = peerSocket.getOutputStream(); num = is.read(buff); } message = new String(buff, 0, num); log("Got authenticated request:\n" + message); log("------------------"); // check provided authorization credentials assertTrue("no proxy-authorization credentials: " + message, message.toLowerCase().indexOf("proxy-authorization:") != -1); } assertTrue(message.startsWith("CONNECT")); // request for SSL tunnel log("Send proxy response"); os.write(proxyResponse.getBytes()); log("Perform SSL Handshake..."); // create sslSocket acting as a remote server peer SSLSocket sslSocket = (SSLSocket) getContext().getSocketFactory().createSocket(peerSocket, "localhost", peerSocket.getPort(), true); // do autoclose sslSocket.setUseClientMode(false); // demand client authentication sslSocket.setNeedClientAuth(true); sslSocket.startHandshake(); peerSocket = sslSocket; is = peerSocket.getInputStream(); os = peerSocket.getOutputStream(); // read the HTTP request sent by secure connection // (HTTPS request) num = is.read(buff); message = new String(buff, 0, num); log("[Remote Server] Request from SSL tunnel:\n" + message); log("------------------"); if (message.startsWith("POST")) { // client connection sent some data log("[Remote Server] try to read client data"); String data = message.substring(message.indexOf("\r\n\r\n")+4); log("[Remote Server] client's data: '" + message + "'"); // check the received data assertEquals(clientsData, data); } log("[Remote Server] Sending the response by SSL tunnel..."); } // send the response with specified response code os.write(("HTTP/1.1 " + responseCode + " Message\r\n" + httpsResponseTail).getBytes()); os.flush(); os.close(); log("Work is DONE actAsProxy=" + actAsProxy); return null; } finally { closeSocket(peerSocket); try { serverSocket.close(); } catch (IOException e) {} } } @Override public String toString() { return actAsProxy ? "Proxy Server" : "Server"; } } /** * The class used for client side work. */ static class ClientConnectionWork extends Work implements Callable<Void> { // connection to be used to contact the server side private HttpsURLConnection connection; /** * Creates the thread acting as a client side. * @param connection connection to be used to contact the server side */ public ClientConnectionWork(HttpsURLConnection connection) { this.connection = connection; log("Created over connection: " + connection.getClass()); } /** * Performs the actual client work. * If some exception occurs during the work it will be * stored in the <code>thrown<code> field. */ public Void call() throws Exception { log("Opening the connection to " + connection.getURL()); connection.connect(); log("Connection has been ESTABLISHED, using proxy: " + connection.usingProxy()); if (connection.getDoOutput()) { log("Posting data"); // connection configured to post data, do so connection.getOutputStream().write(clientsData.getBytes()); } // read the content of HTTP(s) response InputStream is = connection.getInputStream(); log("Input Stream obtained"); byte[] buff = new byte[2048]; int num = 0; int byt = 0; while ((num < buff.length) && ((byt = is.read()) != -1)) { buff[num++] = (byte) byt; } String message = new String(buff, 0, num); log("Got content:\n" + message); log("------------------"); log("Response code: " + connection.getResponseCode()); assertEquals(httpsResponseContent, message); return null; } @Override public String toString() { return "Client Connection"; } } }