Index: modules/luni/src/test/java/org/apache/harmony/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java =================================================================== --- modules/luni/src/test/java/org/apache/harmony/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java (revision 0) +++ modules/luni/src/test/java/org/apache/harmony/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java (revision 0) @@ -0,0 +1,1235 @@ +/* + * Copyright 2006 The Apache Software Foundation or its licensors, as applicable. + * + * Licensed 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.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.TestCase; +import junit.framework.TestSuite; + +/** + * Implementation independent test for HttpsURLConnection. + * The test needs certstore file placed in the directory + * pointed by RESOURCE_DIR system property. The name of the + * certstore file should be "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 RESOURCE_DIR should contain the file "key_store.bks". + *
+ * 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 name of directory containing key/trust store file + public static final String RESOURCE_DIR = + System.getProperty("RESOURCE_DIR", null); + // 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 responce 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(); + } + } + + /** + * 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 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. + * 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, true); + 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, true); + } 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 name depends + * on the RESOURCE_DIR property pointing to the directory containing + * the file named "key_store." + extension equals to the default KeyStore + * type installed in the system in lower case. + * @throws AssertionFailedError if property was not set + * or file does not exist. + */ + private static String getKeyStoreFileName() { + assertNotNull("RESOURCE_DIR property is not set.", RESOURCE_DIR); + String ksFileName = RESOURCE_DIR + File.separator + "key_store" + + "." + KeyStore.getDefaultType().toLowerCase(); + assertTrue("Expected KeyStore file:\n" + ksFileName + + "\nfor default KeyStore of type '" + + KeyStore.getDefaultType() + "' does not exist.", + new File(ksFileName).exists()); + return ksFileName; + } + + /** + * 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() { + 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() { + System.setProperty("javax.net.ssl.keyStoreType", + systemKeyStoreType); + System.setProperty("javax.net.ssl.keyStore", + systemKeyStore); + System.setProperty("javax.net.ssl.keyStorePassword", + systemKeyStorePassword); + + System.setProperty("javax.net.ssl.trustStoreType", + systemTrustStoreType); + System.setProperty("javax.net.ssl.trustStore", + systemTrustStore); + 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); + } + + /** + * 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); + } + + /** + * 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) 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); + + server.start(); + client.start(); + + client.join(); + server.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 (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; + // 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); + } + + /** + * 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 + 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(); + + num = is.read(buff); + 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"); + num = is.read(buff); + message = new String(buff, 0, num); + 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()); + // and return + log("Work is DONE"); + return; + } + + // Do proxy work + if (needProxyAuthentication) { + log("Authentication required ..."); + // send Authentication Request + os.write(respAuthenticationRequired.getBytes()); + // 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); + 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); + } + + // 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()); + 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); + 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); + 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()); + } + 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()); + } + // 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) + && (is.available() > 0) + && ((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()); + + 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); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } +} + Index: modules/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java =================================================================== --- modules/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java (revision 438874) +++ modules/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/https/HttpsURLConnection.java (working copy) @@ -307,11 +307,15 @@ // --------------------------------------------------------------------- // --------------------------------------------------------------------- - /* + /** * HttpsEngine */ private class HttpsEngine extends HttpURLConnection { + // In case of using proxy this field indicates + // if it is a SSL Tunnel establishing stage + private boolean makingSSLTunnel; + protected HttpsEngine(URL url, int port) { super(url, port); } @@ -324,11 +328,63 @@ if (connected) { return; } - super.connect(); - // TODO make SSL Tunnel in case of using the proxy - setUpTransportIO(wrapConnection(socket)); + if (usingProxy() && !makingSSLTunnel) { + // SSL Tunnel through the proxy was not established yet, do so + makingSSLTunnel = true; + // first - make the connection + super.connect(); + // keep request method + String save_meth = method; + // make SSL Tunnel + method = "CONNECT"; + try { + doRequest(); + endRequest(); + } finally { + // restore initial request method + method = save_meth; + } + if (!connected) { + throw new IOException("Could not make SSL Tunneling. " + + "Got response: " + responseMessage + + " (" + responseCode + ")"); + } + // if there are some remaining data in the stream - read it out + InputStream is = socket.getInputStream(); + while (is.available() != 0) { + is.read(); + } + makingSSLTunnel = false; + } else { + // no need in SSL tunnel + super.connect(); + } + if (!makingSSLTunnel) { + setUpTransportIO(wrapConnection(socket)); + } } + protected String requestString() { + if (usingProxy()) { + if (makingSSLTunnel) { + // we are making the SSL Tunneling, return remotehost:port + int port = url.getPort(); + return (port > 0) + ? url.getHost()+":"+port + : url.getHost(); + } else { + // we has made SSL Tunneling, return /requested.data + String file = url.getFile(); + if (file == null || file.length() == 0) { + file = "/"; + } + return file; + } + } else { + return super.requestString(); + } + } + /** * Create the secure socket over the connected socket and * verify remote hostname. Index: modules/luni/build.xml =================================================================== --- modules/luni/build.xml (revision 438874) +++ modules/luni/build.xml (working copy) @@ -305,6 +305,9 @@ + + +