| /* |
| * 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.2"); |
| 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"; |
| } |
| } |
| } |