/* * 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.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.Proxy; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URL; import java.security.KeyStore; import java.security.cert.Certificate; import java.util.Arrays; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; 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.TrustManagerFactory; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import junit.framework.TestSuite; /** * 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.
* 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.
* This certstore file should contain self-signed certificate generated by * keytool utility in a usual way.
* 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; // fields keeping the system values of corresponding properties private static String systemKeyStoreType; private static String systemKeyStore; private static String systemKeyStorePassword; private static String systemTrustStoreType; private static String systemTrustStore; private static String systemTrustStorePassword; /** * 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(); try { SSLSocketFactory defaultSSLSF = HttpsURLConnection .getDefaultSSLSocketFactory(); ServerSocket ss = new ServerSocket(0); Socket s = defaultSSLSF .createSocket("localhost", ss.getLocalPort()); ss.accept(); s.close(); ss.close(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * Checks if HTTPS connection performs initial SSL handshake with the server * working over SSL, sends encrypted HTTP request, and receives expected * HTTP response. After HTTPS session if finished test checks connection * state parameters established by HttpsURLConnection. */ public void testHttpsConnection() throws Throwable { // set up the properties defining the default values needed by SSL stuff setUpStoreProperties(); try { // 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(); // perform the interaction between the peers SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); // check the connection state checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * Checks if HTTPS connection performs initial SSL handshake with the server * working over SSL, sends encrypted HTTP request, and receives expected * HTTP response. After that it checks that the established connection is * persistent. After HTTPS session if finished test checks connection state * parameters established by HttpsURLConnection. */ public void testHttpsPersistentConnection() throws Throwable { // set up the properties defining the default values needed by SSL stuff setUpStoreProperties(); try { // 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(); // perform the interaction between the peers SSLSocket peerSocket = (SSLSocket) doPersistentInteraction( connection, ss); // check the connection state checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * 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(); try { // 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(); 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()); } } // should silently exit connection.connect(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * 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(); try { // 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(); 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(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * 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(); try { // 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.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(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * Tests the behaviour in case of sending the data to the server over * persistent connection. */ public void testPersistence_doOutput() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); try { // 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.setDoOutput(true); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doPersistentInteraction( connection, ss); checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * 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(); try { // 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()))); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * Tests HTTPS connection process made through the proxy server. Checks that * persistent connection to the host exists and can be used no in spite of * explicit Proxy specifying. */ public void testPersistentProxyConnection() throws Throwable { // setting up the properties pointing to the key/trust stores setUpStoreProperties(); try { // 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()))); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doPersistentInteraction( connection, ss); checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * 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(); try { // 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()))); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); checkConnectionStateParameters(connection, peerSocket); // should silently exit connection.connect(); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * 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(); try { // 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()))); // 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()))); // perform the interaction between the peers and check the results peerSocket = (SSLSocket) doInteraction(connection, ss); checkConnectionStateParameters(connection, peerSocket); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * 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(); try { // 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.setDoOutput(true); // perform the interaction between the peers and check the results SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss, OK_CODE); checkConnectionStateParameters(connection, peerSocket); } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * 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(); try { // 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"); HttpURLConnection connection = (HttpURLConnection) url .openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", ss .getLocalPort()))); // perform the interaction between the peers and check the results try { doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE); } catch (IOException e) { // SSL Tunnelling failed if (DO_LOG) { System.out.println("Got expected IOException: " + e.getMessage()); } } } finally { // roll the properties back to system values tearDownStoreProperties(); } } /** * 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(); try { // 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()); HttpURLConnection connection = (HttpURLConnection) url .openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", ss .getLocalPort()))); 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()); } } } finally { // roll the properties back to system values tearDownStoreProperties(); } } // --------------------------------------------------------------------- // ------------------------ Staff Methods ------------------------------ // --------------------------------------------------------------------- /** * Log the name of the test case to be executed. */ public void setUp() throws Exception { if (DO_LOG) { System.out.println(); System.out.println("------------------------"); System.out.println("------ " + getName()); System.out.println("------------------------"); } } /** * 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 AssertionFailedError if property was not set or file does not exist. */ private static String getKeyStoreFileName() throws Exception { String ksFileName = "org/apache/harmony/luni/tests/key_store." + KeyStore.getDefaultType().toLowerCase(); URL url = ClassLoader.getSystemClassLoader().getResource(ksFileName); assertNotNull("Expected KeyStore file: '" + ksFileName + "' for default KeyStore of type '" + KeyStore.getDefaultType() + "' does not exist.", url); return new File(url.toURI()).getAbsolutePath(); } /** * Builds and returns the context used for secure socket creation. */ private static SSLContext getContext() throws Exception { String type = KeyStore.getDefaultType(); SSLContext ctx; String keyStore = getKeyStoreFileName(); File keyStoreFile = new File(keyStore); FileInputStream fis = new FileInputStream(keyStoreFile); KeyStore ks = KeyStore.getInstance(type); ks.load(fis, KS_PASSWORD.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm()); kmf.init(ks, KS_PASSWORD.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(ks); ctx = SSLContext.getInstance("TLSv1"); ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), 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(); systemKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType"); systemKeyStore = System.getProperty("javax.net.ssl.keyStore"); systemKeyStorePassword = System .getProperty("javax.net.ssl.keyStorePassword"); systemTrustStoreType = System .getProperty("javax.net.ssl.trustStoreType"); systemTrustStore = System.getProperty("javax.net.ssl.trustStore"); systemTrustStorePassword = System .getProperty("javax.net.ssl.trustStorePassword"); 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); } /** * Rolls back the values of system properties. */ private static void tearDownStoreProperties() { if (systemKeyStoreType == null) { System.clearProperty("javax.net.ssl.keyStoreType"); } else { System .setProperty("javax.net.ssl.keyStoreType", systemKeyStoreType); } if (systemKeyStore == null) { System.clearProperty("javax.net.ssl.keyStore"); } else { System.setProperty("javax.net.ssl.keyStore", systemKeyStore); } if (systemKeyStorePassword == null) { System.clearProperty("javax.net.ssl.keyStorePassword"); } else { System.setProperty("javax.net.ssl.keyStorePassword", systemKeyStorePassword); } if (systemTrustStoreType == null) { System.clearProperty("javax.net.ssl.trustStoreType"); } else { System.setProperty("javax.net.ssl.trustStoreType", systemTrustStoreType); } if (systemTrustStore == null) { System.clearProperty("javax.net.ssl.trustStore"); } else { System.setProperty("javax.net.ssl.trustStore", systemTrustStore); } if (systemTrustStorePassword == null) { System.clearProperty("javax.net.ssl.trustStorePassword"); } else { System.setProperty("javax.net.ssl.trustStorePassword", systemTrustStorePassword); } } /** * Performs interaction between client's HttpURLConnection and servers side * (ServerSocket). */ public static Socket doInteraction( final HttpURLConnection clientConnection, final ServerSocket serverSocket) throws Throwable { return doInteraction(clientConnection, serverSocket, OK_CODE, false, false); } /** * Performs interaction between client's HttpURLConnection and servers side * (ServerSocket). Server will response with specified response code. */ public static Socket doInteraction( final HttpURLConnection clientConnection, final ServerSocket serverSocket, final int responseCode) throws Throwable { return doInteraction(clientConnection, serverSocket, responseCode, false, false); } /** * Performs interaction between client's HttpURLConnection and servers side * (ServerSocket) over persistent connection. */ public static Socket doPersistentInteraction( final HttpURLConnection clientConnection, final ServerSocket serverSocket) throws Throwable { return doInteraction(clientConnection, serverSocket, OK_CODE, false, true); } /** * Performs interaction between client's HttpURLConnection and servers side * (ServerSocket) over persistent connection. Server will response with * specified response code. */ public static Socket doPersistentInteraction( final HttpURLConnection clientConnection, final ServerSocket serverSocket, final int responseCode) throws Throwable { return doInteraction(clientConnection, serverSocket, responseCode, false, true); } /** * Performs interaction between client's HttpURLConnection 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 HttpURLConnection clientConnection, final ServerSocket serverSocket, final int responseCode, final boolean doAuthentication, final boolean checkPersistence) throws Throwable { // set up the connection clientConnection.setDoInput(true); clientConnection.setConnectTimeout(TIMEOUT); clientConnection.setReadTimeout(TIMEOUT); ServerWork server = new ServerWork(serverSocket, responseCode, doAuthentication, checkPersistence); ClientConnectionWork client = new ClientConnectionWork(clientConnection); server.start(); client.start(); client.join(); if (client.thrown != null) { if (responseCode != OK_CODE) { // not OK response expected // it is probably expected exception, keep it as is throw client.thrown; } if ((client.thrown instanceof SocketTimeoutException) && (server.thrown != null)) { // server's exception is more informative in this case throw new Exception(server.thrown); } else { throw new Exception(client.thrown); } } if (checkPersistence) { ClientConnectionWork client2 = new ClientConnectionWork( (HttpURLConnection) clientConnection.getURL() .openConnection()); client2.start(); client2.join(); if (client2.thrown != null) { if (responseCode != OK_CODE) { // not OK response expected // it is probably expected exception, keep it as is throw client2.thrown; } if ((client2.thrown instanceof SocketTimeoutException) && (server.thrown != null)) { // server's exception is more informative in this case throw new Exception(server.thrown); } else { throw new Exception(client2.thrown); } } } server.join(); if (server.thrown != null) { throw server.thrown; } 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 extends Thread { /** * The header of OK HTTP response. */ static String responseHead = "HTTP/1.1 200 OK\n"; /** * The content of the response. */ static String plainResponseContent = "\n" + "Plain Response Content\n" + ""; /** * The tail of the response. */ static String plainResponseTail = "Content-type: text/html\n" + "Content-length: " + plainResponseContent.length() + "\n\n" + plainResponseContent; /** * The response message to be sent in plain (HTTP) format. */ static String plainResponse = responseHead + plainResponseTail; /** * The content of the response to be sent during HTTPS session. */ static String httpsResponseContent = "\n" + "HTTPS Response Content\n" + ""; /** * The tail of the response to be sent during HTTPS session. */ static String httpsResponseTail = "Content-type: text/html\n" + "Content-length: " + httpsResponseContent.length() + "\n\n" + httpsResponseContent; /** * The response requiring client's proxy authentication. */ static String respAuthenticationRequired = "HTTP/1.0 407 Proxy authentication required\n" + "Proxy-authenticate: Basic realm=\"localhost\"\n\n"; /** * The data to be posted by client to the server. */ static String clientsData = "_.-^ Client's Data ^-._"; /** * The exception thrown during peers interaction. */ protected Throwable thrown; /** * The print stream used for debug log. If it is null debug info will * not be printed. */ private PrintStream out = new PrintStream(System.out); /** * Prints log message. */ public synchronized void log(String message) { if (DO_LOG && (out != null)) { System.out.println("[" + getName() + "]: " + message); } } } /** * The class used for server side works. */ static class ServerWork extends Work { // the server socket used for connection private ServerSocket serverSocket; // the socket connected with client peer private Socket peerSocket; // indicates if the server acts as proxy server private boolean actAsProxy; // indicates if the server needs proxy authentication private boolean needProxyAuthentication; // do we check for connection persistence private boolean checkPersistence; // response code to be send to the client peer private int responseCode; /** * Creates the thread acting as a server side. */ public ServerWork(ServerSocket serverSocket) { // the server does not require proxy authentication // and sends OK_CODE (OK) response code this(serverSocket, OK_CODE, false, false); } /** * 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, boolean checkPersistence) { this.serverSocket = serverSocket; this.responseCode = responseCode; this.needProxyAuthentication = needProxyAuthentication; this.checkPersistence = checkPersistence; // will act as a proxy server if the specified server socket // is not a secure server socket if (serverSocket instanceof SSLServerSocket) { // demand client to send its certificate ((SSLServerSocket) serverSocket).setNeedClientAuth(true); // work as a HTTPS server, not as HTTP proxy this.actAsProxy = false; } else { this.actAsProxy = true; } this.actAsProxy = !(serverSocket instanceof SSLServerSocket); setName(this.actAsProxy ? "Proxy Server" : "Server"); } /** * Closes the connection. */ public void closeSocket(Socket socket) { 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 thrown field. */ public void run() { // the buffer used for reading the messages byte[] buff = new byte[2048]; // the number of bytes read into the buffer int num; 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(); // how many times established connection will be used int number_of_uses = checkPersistence ? 2 : 1; for (int it = 0; it < number_of_uses; it++) { if (checkPersistence) { log("=========================================="); log("Use established connection for " + (it + 1) + " time"); } num = is.read(buff); String message = new String(buff, 0, num, "UTF-8"); 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"); num = is.read(buff); message = new String(buff, 0, num, "UTF-8"); log("client's data: '" + message + "'"); // check the received data assertEquals(clientsData, message); } // just send the response os .write(("HTTP/1.1 " + responseCode + "\n" + httpsResponseTail) .getBytes("UTF-8")); log("Simple NON-Proxy work is DONE"); continue; } // Do proxy work if (needProxyAuthentication) { log("Authentication required ..."); // send Authentication Request os.write(respAuthenticationRequired.getBytes("UTF-8")); // read response 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, "UTF-8"); log("Got authenticated request:\n" + message); log("------------------"); // check provided authorization credentials assertTrue("Received message does not contain " + "authorization credentials", message.toLowerCase().indexOf( "proxy-authorization:") > 0); } if (peerSocket instanceof SSLSocket) { // it will be so if we are have second iteration // over persistent connection os .write(("HTTP/1.1 " + OK_CODE + "\n" + httpsResponseTail) .getBytes("UTF-8")); log("Sent OK RESPONSE over SSL"); } else { // The content of this response will reach proxied // HTTPUC but will not reach proxied HTTPSUC // In case of HTTP connection it will be the final // message, in case of HTTPS connection this message // will just indicate that connection with remote // host has been done // (i.e. SSL tunnel has been established). os.write(plainResponse.getBytes("UTF-8")); log("Sent OK RESPONSE"); } if (message.startsWith("CONNECT")) { // request for SSL // tunnel 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, "UTF-8"); 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"); num = is.read(buff); message = new String(buff, 0, num, "UTF-8"); log("[Remote Server] client's data: '" + message + "'"); // check the received data assertEquals(clientsData, message); } log("[Remote Server] Sending the response by SSL tunnel.."); // send the response with specified response code os .write(("HTTP/1.1 " + responseCode + "\n" + httpsResponseTail) .getBytes("UTF-8")); } log("Work is DONE"); } ; } catch (Throwable e) { if (DO_LOG) { e.printStackTrace(); } thrown = e; } finally { closeSocket(peerSocket); try { serverSocket.close(); } catch (IOException e) { } } } } /** * The class used for client side works. It could be used to test both * HttpURLConnection and HttpsURLConnection. */ static class ClientConnectionWork extends Work { // connection to be used to contact the server side private HttpURLConnection connection; /** * Creates the thread acting as a client side. * * @param connection connection to be used to contact the server side */ public ClientConnectionWork(HttpURLConnection connection) { this.connection = connection; setName("Client 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 thrown field. */ public void run() { try { log("Opening the connection.."); connection.connect(); log("Connection has been ESTABLISHED, using proxy: " + connection.usingProxy()); if (connection.getDoOutput()) { // connection configured to post data, do so connection.getOutputStream().write(clientsData.getBytes("UTF-8")); } // read the content of HTTP(s) response InputStream is = connection.getInputStream(); log("Input Stream obtained: " + is.getClass()); byte[] buff = new byte[2048]; int num = 0; int byt = 0; while ((num < buff.length) && (is.available() > 0) && ((byt = is.read()) != -1)) { buff[num++] = (byte) byt; } String message = new String(buff, 0, num, "UTF-8"); log("Got content:\n" + message); log("------------------"); log("Response code: " + connection.getResponseCode()); if (connection instanceof HttpsURLConnection) { assertEquals(httpsResponseContent, message); } else { assertEquals(plainResponseContent, message); } } catch (Throwable e) { if (DO_LOG) { e.printStackTrace(); } thrown = e; } } } public static junit.framework.Test suite() { return new TestSuite(HttpsURLConnectionTest.class); } }