| /* |
| * 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 com.google.mockwebserver.Dispatcher; |
| import com.google.mockwebserver.MockResponse; |
| import com.google.mockwebserver.MockWebServer; |
| import com.google.mockwebserver.RecordedRequest; |
| import com.google.mockwebserver.SocketPolicy; |
| |
| 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.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.Collections; |
| import java.util.LinkedList; |
| |
| 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.SSLSession; |
| 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. |
| */ |
| public class HttpsURLConnectionTest extends TestCase { |
| |
| private static final String POST_METHOD = "POST"; |
| |
| private static final String GET_METHOD = "GET"; |
| |
| /** |
| * Data to be posted by client to the server when the method is POST. |
| */ |
| private static final String POST_DATA = "_.-^ Client's Data ^-._"; |
| |
| /** |
| * The content of the response to be sent during HTTPS session. |
| */ |
| private static final String RESPONSE_CONTENT |
| = "<HTML>\n" |
| + "<HEAD><TITLE>HTTPS Response Content</TITLE></HEAD>\n" |
| + "</HTML>"; |
| |
| // 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 pointing to the key/trust stores |
| 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 pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| SSLContext ctx = getContext(); |
| |
| // set the HostnameVerifier required to satisfy SSL - always returns "verified". |
| HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); |
| |
| // create a webserver to check and respond to requests |
| SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); |
| MockWebServer webServer = createWebServer(ctx, dispatcher); |
| |
| // create url connection to be tested |
| URL url = webServer.getUrl("/"); |
| HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); |
| connection.setSSLSocketFactory(ctx.getSocketFactory()); |
| |
| // perform the interaction between the peers |
| executeClientRequest(connection, false /* doOutput */); |
| |
| checkConnectionStateParameters(connection, dispatcher.getLastRequest()); |
| |
| // should silently exit |
| connection.connect(); |
| |
| webServer.shutdown(); |
| } |
| |
| /** |
| * 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 pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| SSLContext ctx = getContext(); |
| |
| // set the HostnameVerifier required to satisfy SSL - always returns "verified". |
| HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); |
| |
| // create a webserver to check and respond to requests |
| SingleRequestDispatcher dispatcher = |
| new SingleRequestDispatcher(GET_METHOD, NOT_FOUND_CODE); |
| MockWebServer webServer = createWebServer(ctx, dispatcher); |
| |
| // create url connection to be tested |
| URL url = webServer.getUrl("/"); |
| HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); |
| connection.setSSLSocketFactory(ctx.getSocketFactory()); |
| |
| try { |
| executeClientRequest(connection, false /* doOutput */); |
| 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(); |
| |
| webServer.shutdown(); |
| } |
| |
| /** |
| * Tests possibility to set up the default SSLSocketFactory to be used by HttpsURLConnection. |
| */ |
| public void testSetDefaultSSLSocketFactory() throws Throwable { |
| // set up the properties pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| SSLContext ctx = getContext(); |
| |
| SSLSocketFactory socketFactory = ctx.getSocketFactory(); |
| // set up the factory as default |
| HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); |
| // check the result |
| assertSame("Default SSLSocketFactory differs from expected", |
| socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory()); |
| |
| // set the initial default host name verifier. |
| TestHostnameVerifier initialHostnameVerifier = new TestHostnameVerifier(); |
| HttpsURLConnection.setDefaultHostnameVerifier(initialHostnameVerifier); |
| |
| // create a webserver to check and respond to requests |
| SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); |
| MockWebServer webServer = createWebServer(ctx, dispatcher); |
| |
| // create HttpsURLConnection to be tested |
| URL url = webServer.getUrl("/"); |
| HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); |
| |
| // late initialization: this HostnameVerifier should not be used for created connection |
| TestHostnameVerifier lateHostnameVerifier = new TestHostnameVerifier(); |
| HttpsURLConnection.setDefaultHostnameVerifier(lateHostnameVerifier); |
| |
| // perform the interaction between the peers |
| executeClientRequest(connection, false /* doOutput */); |
| checkConnectionStateParameters(connection, dispatcher.getLastRequest()); |
| |
| // check the verification process |
| assertTrue("Hostname verification was not done", initialHostnameVerifier.verified); |
| assertFalse("Hostname verification should not be done by this verifier", |
| lateHostnameVerifier.verified); |
| // check the used SSLSocketFactory |
| assertSame("Default SSLSocketFactory should be used", |
| HttpsURLConnection.getDefaultSSLSocketFactory(), |
| connection.getSSLSocketFactory()); |
| |
| webServer.shutdown(); |
| } |
| |
| /** |
| * Tests |
| * {@link javax.net.ssl.HttpsURLConnection#setSSLSocketFactory(javax.net.ssl.SSLSocketFactory)}. |
| */ |
| public void testSetSSLSocketFactory() throws Throwable { |
| // set up the properties pointing to the key/trust stores |
| SSLContext ctx = getContext(); |
| |
| // set the initial default host name verifier. |
| TestHostnameVerifier hostnameVerifier = new TestHostnameVerifier(); |
| HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); |
| |
| // create a webserver to check and respond to requests |
| SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); |
| MockWebServer webServer = createWebServer(ctx, dispatcher); |
| |
| // create HttpsURLConnection to be tested |
| URL url = webServer.getUrl("/"); |
| HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); |
| |
| // late initialization: should not be used for the created connection. |
| SSLSocketFactory socketFactory = ctx.getSocketFactory(); |
| connection.setSSLSocketFactory(socketFactory); |
| |
| // late initialization: should not be used for created connection |
| TestHostnameVerifier lateHostnameVerifier = new TestHostnameVerifier(); |
| HttpsURLConnection.setDefaultHostnameVerifier(lateHostnameVerifier); |
| |
| // perform the interaction between the peers |
| executeClientRequest(connection, false /* doOutput */); |
| checkConnectionStateParameters(connection, dispatcher.getLastRequest()); |
| // check the verification process |
| assertTrue("Hostname verification was not done", hostnameVerifier.verified); |
| assertFalse("Hostname verification should not be done by this verifier", |
| lateHostnameVerifier.verified); |
| // check the used SSLSocketFactory |
| assertNotSame("Default SSLSocketFactory should not be used", |
| HttpsURLConnection.getDefaultSSLSocketFactory(), |
| connection.getSSLSocketFactory()); |
| assertSame("Result differs from expected", socketFactory, connection.getSSLSocketFactory()); |
| |
| webServer.shutdown(); |
| } |
| |
| /** |
| * 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 { |
| // set up the properties pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| SSLContext ctx = getContext(); |
| |
| TestHostnameVerifier defaultHostnameVerifier = new TestHostnameVerifier(); |
| HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); |
| |
| // create a webserver to check and respond to requests |
| SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); |
| MockWebServer webServer = createWebServer(ctx, dispatcher); |
| |
| // create HttpsURLConnection to be tested |
| URL url = webServer.getUrl("/"); |
| HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); |
| connection.setSSLSocketFactory(getContext().getSocketFactory()); |
| |
| // replace the default verifier |
| TestHostnameVerifier connectionHostnameVerifier = new TestHostnameVerifier(); |
| connection.setHostnameVerifier(connectionHostnameVerifier); |
| |
| // perform the interaction between the peers and check the results |
| executeClientRequest(connection, false /* doOutput */); |
| assertTrue("Hostname verification was not done", connectionHostnameVerifier.verified); |
| assertFalse("Hostname verification should not be done by this verifier", |
| defaultHostnameVerifier.verified); |
| |
| checkConnectionStateParameters(connection, dispatcher.getLastRequest()); |
| |
| webServer.shutdown(); |
| } |
| |
| /** |
| * Tests the behaviour in case of sending the data to the server. |
| */ |
| public void test_doOutput() throws Throwable { |
| // set up the properties pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| SSLContext ctx = getContext(); |
| |
| // create a webserver to check and respond to requests |
| SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(POST_METHOD, OK_CODE); |
| MockWebServer webServer = createWebServer(ctx, dispatcher); |
| |
| // set the HostnameVerifier required to satisfy SSL - always returns "verified". |
| HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); |
| |
| // create HttpsURLConnection to be tested |
| URL url = webServer.getUrl("/"); |
| HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); |
| connection.setSSLSocketFactory(getContext().getSocketFactory()); |
| |
| // perform the interaction between the peers and check the results |
| executeClientRequest(connection, true /* doOutput */); |
| checkConnectionStateParameters(connection, dispatcher.getLastRequest()); |
| |
| // should silently exit |
| connection.connect(); |
| |
| webServer.shutdown(); |
| } |
| |
| /** |
| * Tests HTTPS connection process made through the proxy server. |
| */ |
| public void testProxyConnection() throws Throwable { |
| // set up the properties pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| SSLContext ctx = getContext(); |
| |
| // set the HostnameVerifier required to satisfy SSL - always returns "verified". |
| HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); |
| |
| // create a server that pretends to be both a proxy and then the webserver |
| // request 1: proxy CONNECT, respond with OK |
| ProxyConnectDispatcher proxyConnectDispatcher = |
| new ProxyConnectDispatcher(false /* authenticationRequired */); |
| // request 2: tunnelled GET, respond with OK |
| SingleRequestDispatcher getDispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); |
| DelegatingDispatcher delegatingDispatcher = |
| new DelegatingDispatcher(proxyConnectDispatcher, getDispatcher); |
| MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher); |
| |
| // create HttpsURLConnection to be tested |
| URL proxyUrl = proxyAndWebServer.getUrl("/"); |
| InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); |
| URL url = new URL("https://requested.host:55556/requested.data"); |
| HttpsURLConnection connection = (HttpsURLConnection) |
| url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); |
| connection.setSSLSocketFactory(getContext().getSocketFactory()); |
| |
| // perform the interaction between the peers and check the results |
| executeClientRequest(connection, false /* doOutput */); |
| checkConnectionStateParameters(connection, getDispatcher.getLastRequest()); |
| |
| // should silently exit |
| connection.connect(); |
| |
| proxyAndWebServer.shutdown(); |
| } |
| |
| /** |
| * Tests HTTPS connection process made through the proxy server. |
| * Proxy server needs authentication. |
| */ |
| public void testProxyAuthConnection() throws Throwable { |
| // set up the properties pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| SSLContext ctx = getContext(); |
| |
| // set the HostnameVerifier required to satisfy SSL - always returns "verified". |
| HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); |
| |
| Authenticator.setDefault(new Authenticator() { |
| protected PasswordAuthentication getPasswordAuthentication() { |
| return new PasswordAuthentication("user", "password".toCharArray()); |
| } |
| }); |
| |
| // create a server that pretends to be both a proxy and then the webserver |
| // request 1: proxy CONNECT, respond with auth challenge |
| ProxyConnectAuthFailDispatcher authFailDispatcher = new ProxyConnectAuthFailDispatcher(); |
| // request 2: proxy CONNECT, respond with OK |
| ProxyConnectDispatcher proxyConnectDispatcher = |
| new ProxyConnectDispatcher(true /* authenticationRequired */); |
| // request 3: tunnelled GET, respond with OK |
| SingleRequestDispatcher getDispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); |
| DelegatingDispatcher delegatingDispatcher = new DelegatingDispatcher( |
| authFailDispatcher, proxyConnectDispatcher, getDispatcher); |
| MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher); |
| |
| // create HttpsURLConnection to be tested |
| URL proxyUrl = proxyAndWebServer.getUrl("/"); |
| InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); |
| URL url = new URL("https://requested.host:55555/requested.data"); |
| HttpsURLConnection connection = (HttpsURLConnection) |
| url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); |
| connection.setSSLSocketFactory(getContext().getSocketFactory()); |
| |
| // perform the interaction between the peers and check the results |
| executeClientRequest(connection, false /* doOutput */); |
| checkConnectionStateParameters(connection, getDispatcher.getLastRequest()); |
| |
| // should silently exit |
| connection.connect(); |
| |
| proxyAndWebServer.shutdown(); |
| } |
| |
| /** |
| * Tests HTTPS connection process made through the proxy server. |
| * Two HTTPS connections are opened for one URL: the first time the connection is opened |
| * through one proxy, the second time it is opened through another. |
| */ |
| public void testConsequentProxyConnection() throws Throwable { |
| // set up the properties pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| // set the HostnameVerifier required to satisfy SSL - always returns "verified". |
| HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); |
| |
| // create a server that pretends to be both a proxy and then the webserver |
| SingleRequestDispatcher getDispatcher1 = new SingleRequestDispatcher(GET_METHOD, OK_CODE); |
| MockWebServer proxyAndWebServer1 = createProxiedServer(getDispatcher1); |
| |
| // create HttpsURLConnection to be tested |
| URL proxyUrl1 = proxyAndWebServer1.getUrl("/"); |
| URL url = new URL("https://requested.host:55555/requested.data"); |
| InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl1.getPort()); |
| HttpsURLConnection connection = (HttpsURLConnection) |
| url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); |
| connection.setSSLSocketFactory(getContext().getSocketFactory()); |
| executeClientRequest(connection, false /* doOutput */); |
| checkConnectionStateParameters(connection, getDispatcher1.getLastRequest()); |
| |
| proxyAndWebServer1.shutdown(); |
| |
| // create another server |
| SingleRequestDispatcher getDispatcher2 = new SingleRequestDispatcher(GET_METHOD, OK_CODE); |
| MockWebServer proxyAndWebServer2 = createProxiedServer(getDispatcher2); |
| |
| // create another HttpsURLConnection to be tested |
| URL proxyUrl2 = proxyAndWebServer2.getUrl("/"); |
| InetSocketAddress proxyAddress2 = new InetSocketAddress("localhost", proxyUrl2.getPort()); |
| HttpsURLConnection connection2 = (HttpsURLConnection) url.openConnection( |
| new Proxy(Proxy.Type.HTTP, proxyAddress2)); |
| connection2.setSSLSocketFactory(getContext().getSocketFactory()); |
| |
| // perform the interaction between the peers and check the results |
| executeClientRequest(connection2, false /* doOutput */); |
| checkConnectionStateParameters(connection2, getDispatcher2.getLastRequest()); |
| |
| proxyAndWebServer2.shutdown(); |
| } |
| |
| private static MockWebServer createProxiedServer(Dispatcher getDispatcher) |
| throws Exception { |
| // request 1: proxy CONNECT, respond with OK |
| ProxyConnectDispatcher proxyConnectDispatcher = |
| new ProxyConnectDispatcher(false /* authenticationRequired */); |
| // request 2: The get dispatcher. |
| DelegatingDispatcher delegatingDispatcher1 = |
| new DelegatingDispatcher(proxyConnectDispatcher, getDispatcher); |
| return createProxyAndWebServer(getContext(), delegatingDispatcher1); |
| } |
| |
| /** |
| * 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 { |
| // set up the properties pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| SSLContext ctx = getContext(); |
| |
| // set the HostnameVerifier required to satisfy SSL - always returns "verified". |
| HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); |
| |
| Authenticator.setDefault(new Authenticator() { |
| protected PasswordAuthentication getPasswordAuthentication() { |
| return new PasswordAuthentication("user", "password".toCharArray()); |
| } |
| }); |
| |
| // create a server that pretends to be both a proxy and then the webserver |
| // request 1: proxy CONNECT, respond with auth challenge |
| ProxyConnectAuthFailDispatcher authFailDispatcher = new ProxyConnectAuthFailDispatcher(); |
| // request 2: proxy CONNECT, respond with OK |
| ProxyConnectDispatcher proxyConnectDispatcher = |
| new ProxyConnectDispatcher(true /* authenticationRequired */); |
| // request 3: tunnelled POST, respond with OK |
| SingleRequestDispatcher postDispatcher = new SingleRequestDispatcher(POST_METHOD, OK_CODE); |
| DelegatingDispatcher delegatingDispatcher = new DelegatingDispatcher( |
| authFailDispatcher, proxyConnectDispatcher, postDispatcher); |
| MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher); |
| URL proxyUrl = proxyAndWebServer.getUrl("/"); |
| |
| // create HttpsURLConnection to be tested |
| InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); |
| HttpsURLConnection connection = (HttpsURLConnection) |
| proxyUrl.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); |
| connection.setSSLSocketFactory(getContext().getSocketFactory()); |
| |
| // perform the interaction between the peers and check the results |
| executeClientRequest(connection, true /* doOutput */); |
| checkConnectionStateParameters(connection, postDispatcher.getLastRequest()); |
| |
| // should silently exit |
| connection.connect(); |
| |
| proxyAndWebServer.shutdown(); |
| } |
| |
| /** |
| * 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 { |
| // set up the properties pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| // set the HostnameVerifier required to satisfy SSL - always returns "verified". |
| HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); |
| |
| // create a server that pretends to be both a proxy that requests authentication. |
| MockWebServer proxyAndWebServer = new MockWebServer(); |
| ProxyConnectAuthFailDispatcher authFailDispatcher = new ProxyConnectAuthFailDispatcher(); |
| proxyAndWebServer.setDispatcher(authFailDispatcher); |
| proxyAndWebServer.play(); |
| |
| // create HttpsURLConnection to be tested |
| URL proxyUrl = proxyAndWebServer.getUrl("/"); |
| InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); |
| URL url = new URL("https://requested.host:55555/requested.data"); |
| HttpsURLConnection connection = (HttpsURLConnection) |
| url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); |
| connection.setSSLSocketFactory(getContext().getSocketFactory()); |
| |
| // perform the interaction between the peers and check the results |
| try { |
| executeClientRequest(connection, false); |
| } catch (IOException e) { |
| // SSL Tunnelling failed |
| if (DO_LOG) { |
| System.out.println("Got expected IOException: " + e.getMessage()); |
| } |
| } |
| proxyAndWebServer.shutdown(); |
| } |
| |
| /** |
| * Tests the behaviour of HTTPS connection in case of unavailability of requested resource (as |
| * reported by the target web server). |
| */ |
| public void testProxyConnection_Not_Found_Response() throws Throwable { |
| // set up the properties pointing to the key/trust stores |
| setUpStoreProperties(); |
| |
| SSLContext ctx = getContext(); |
| |
| // set the HostnameVerifier required to satisfy SSL - always returns "verified". |
| HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); |
| |
| // create a server that pretends to be a proxy |
| ProxyConnectDispatcher proxyConnectDispatcher = |
| new ProxyConnectDispatcher(false /* authenticationRequired */); |
| SingleRequestDispatcher notFoundDispatcher = |
| new SingleRequestDispatcher(GET_METHOD, NOT_FOUND_CODE); |
| DelegatingDispatcher delegatingDispatcher = |
| new DelegatingDispatcher(proxyConnectDispatcher, notFoundDispatcher); |
| MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher); |
| |
| // create HttpsURLConnection to be tested |
| URL proxyUrl = proxyAndWebServer.getUrl("/"); |
| InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); |
| URL url = new URL("https://requested.host:55555/requested.data"); |
| HttpsURLConnection connection = (HttpsURLConnection) |
| url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); |
| connection.setSSLSocketFactory(getContext().getSocketFactory()); |
| |
| try { |
| executeClientRequest(connection, false /* doOutput */); |
| fail("Expected exception was not thrown."); |
| } catch (FileNotFoundException e) { |
| if (DO_LOG) { |
| System.out.println("Expected exception was thrown: " + e.getMessage()); |
| } |
| } |
| proxyAndWebServer.shutdown(); |
| } |
| |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| if (DO_LOG) { |
| // Log the name of the test case to be executed. |
| 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(); |
| } |
| } |
| |
| private static void checkConnectionStateParameters( |
| HttpsURLConnection connection, RecordedRequest request) throws Exception { |
| assertEquals(request.getSslCipherSuite(), connection.getCipherSuite()); |
| assertEquals(request.getSslLocalPrincipal(), connection.getPeerPrincipal()); |
| assertEquals(request.getSslPeerPrincipal(), connection.getLocalPrincipal()); |
| |
| Certificate[] serverCertificates = connection.getServerCertificates(); |
| Certificate[] localCertificates = request.getSslLocalCertificates(); |
| assertTrue("Server certificates differ from expected", |
| Arrays.equals(serverCertificates, localCertificates)); |
| |
| localCertificates = connection.getLocalCertificates(); |
| serverCertificates = request.getSslPeerCertificates(); |
| assertTrue("Local certificates differ from expected", |
| Arrays.equals(serverCertificates, localCertificates)); |
| } |
| |
| /** |
| * Returns the file name of the key/trust store. The key store file |
| * (named as "key_store." + extension equals to the default KeyStore |
| * type installed in the system in lower case) is searched in classpath. |
| * @throws junit.framework.AssertionFailedError if property was not set |
| * or file does not exist. |
| */ |
| private static String getKeyStoreFileName() { |
| return store.getAbsolutePath(); |
| } |
| |
| /** |
| * Builds and returns the context used for secure socket creation. |
| */ |
| private static SSLContext getContext() throws Exception { |
| String type = KeyStore.getDefaultType(); |
| String keyStore = getKeyStoreFileName(); |
| File keyStoreFile = new File(keyStore); |
| FileInputStream fis = new FileInputStream(keyStoreFile); |
| |
| KeyStore ks = KeyStore.getInstance(type); |
| ks.load(fis, KS_PASSWORD.toCharArray()); |
| fis.close(); |
| if (DO_LOG && false) { |
| TestKeyStore.dump("HttpsURLConnection.getContext", ks, KS_PASSWORD.toCharArray()); |
| } |
| |
| String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); |
| KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm); |
| kmf.init(ks, KS_PASSWORD.toCharArray()); |
| KeyManager[] keyManagers = kmf.getKeyManagers(); |
| |
| String tmfAlgorthm = TrustManagerFactory.getDefaultAlgorithm(); |
| TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorthm); |
| tmf.init(ks); |
| TrustManager[] trustManagers = tmf.getTrustManagers(); |
| if (DO_LOG) { |
| trustManagers = TestTrustManager.wrap(trustManagers); |
| } |
| |
| SSLContext ctx = SSLContext.getInstance("TLSv1"); |
| ctx.init(keyManagers, trustManagers, null); |
| return ctx; |
| } |
| |
| /** |
| * Sets up the properties pointing to the key store and trust store |
| * and used as default values by JSSE staff. This is needed to test |
| * HTTPS behaviour in the case of default SSL Socket Factories. |
| */ |
| private static void setUpStoreProperties() throws Exception { |
| String type = KeyStore.getDefaultType(); |
| |
| System.setProperty("javax.net.ssl.keyStoreType", type); |
| System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName()); |
| System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD); |
| |
| System.setProperty("javax.net.ssl.trustStoreType", type); |
| System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName()); |
| System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD); |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * Creates a {@link MockWebServer} that acts as both a proxy and then a web server with the |
| * supplied {@link SSLContext} and {@link Dispatcher}. The dispatcher provided must handle the |
| * CONNECT request/responses and {@link SocketPolicy} needed to simulate the hand-off from proxy |
| * to web server. See {@link HttpsURLConnectionTest.ProxyConnectDispatcher}. |
| */ |
| private static MockWebServer createProxyAndWebServer(SSLContext ctx, Dispatcher dispatcher) |
| throws IOException { |
| return createServer(ctx, dispatcher, true /* handleProxying */); |
| } |
| |
| /** |
| * Creates a {@link MockWebServer} that acts as (only) a web server with the supplied |
| * {@link SSLContext} and {@link Dispatcher}. |
| */ |
| private static MockWebServer createWebServer(SSLContext ctx, Dispatcher dispatcher) |
| throws IOException { |
| return createServer(ctx, dispatcher, false /* handleProxying */); |
| } |
| |
| private static MockWebServer createServer( |
| SSLContext ctx, Dispatcher dispatcher, boolean handleProxying) |
| throws IOException { |
| MockWebServer webServer = new MockWebServer(); |
| webServer.useHttps(ctx.getSocketFactory(), handleProxying /* tunnelProxy */); |
| webServer.setDispatcher(dispatcher); |
| webServer.play(); |
| return webServer; |
| } |
| |
| /** |
| * A {@link Dispatcher} that has a list of dispatchers to delegate to, each of which will be |
| * used for one request and then discarded. |
| */ |
| private static class DelegatingDispatcher extends Dispatcher { |
| private LinkedList<Dispatcher> delegates = new LinkedList<Dispatcher>(); |
| |
| public DelegatingDispatcher(Dispatcher... dispatchers) { |
| addAll(dispatchers); |
| } |
| |
| private void addAll(Dispatcher... dispatchers) { |
| Collections.addAll(delegates, dispatchers); |
| } |
| |
| @Override |
| public MockResponse dispatch(RecordedRequest request) throws InterruptedException { |
| return delegates.removeFirst().dispatch(request); |
| } |
| |
| @Override |
| public MockResponse peek() { |
| return delegates.getFirst().peek(); |
| } |
| } |
| |
| /** Handles a request for SSL tunnel: Answers with a request to authenticate. */ |
| private static class ProxyConnectAuthFailDispatcher extends Dispatcher { |
| |
| @Override |
| public MockResponse dispatch(RecordedRequest request) throws InterruptedException { |
| assertEquals("CONNECT", request.getMethod()); |
| |
| MockResponse response = new MockResponse(); |
| response.setResponseCode(AUTHENTICATION_REQUIRED_CODE); |
| response.addHeader("Proxy-authenticate: Basic realm=\"localhost\""); |
| log("Authentication required. Sending response: " + response); |
| return response; |
| } |
| |
| private void log(String msg) { |
| HttpsURLConnectionTest.log("ProxyConnectAuthFailDispatcher", msg); |
| } |
| } |
| |
| /** |
| * Handles a request for SSL tunnel: Answers with a success and the socket is upgraded to SSL. |
| */ |
| private static class ProxyConnectDispatcher extends Dispatcher { |
| |
| private final boolean authenticationRequired; |
| |
| private ProxyConnectDispatcher(boolean authenticationRequired) { |
| this.authenticationRequired = authenticationRequired; |
| } |
| |
| @Override |
| public MockResponse dispatch(RecordedRequest request) throws InterruptedException { |
| if (authenticationRequired) { |
| // check provided authorization credentials |
| assertNotNull("no proxy-authorization credentials: " + request, |
| request.getHeader("proxy-authorization")); |
| log("Got authenticated request:\n" + request); |
| log("------------------"); |
| } |
| |
| assertEquals("CONNECT", request.getMethod()); |
| log("Send proxy response"); |
| MockResponse response = new MockResponse(); |
| response.setResponseCode(200); |
| response.setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END); |
| return response; |
| } |
| |
| @Override |
| public MockResponse peek() { |
| return new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END); |
| } |
| |
| private void log(String msg) { |
| HttpsURLConnectionTest.log("ProxyConnectDispatcher", msg); |
| } |
| } |
| |
| /** |
| * Handles a request: Answers with a response with a specified status code. |
| * If the {@code expectedMethod} is {@code POST} a hardcoded response body {@link #POST_DATA} |
| * will be included in the response. |
| */ |
| private static class SingleRequestDispatcher extends Dispatcher { |
| |
| private final String expectedMethod; |
| private final int responseCode; |
| |
| private RecordedRequest lastRequest; |
| |
| private SingleRequestDispatcher(String expectedMethod, int responseCode) { |
| this.responseCode = responseCode; |
| this.expectedMethod = expectedMethod; |
| } |
| |
| @Override |
| public MockResponse dispatch(RecordedRequest request) throws InterruptedException { |
| if (lastRequest != null) { |
| fail("More than one request received"); |
| } |
| log("Request received: " + request); |
| lastRequest = request; |
| assertEquals(expectedMethod, request.getMethod()); |
| if (POST_METHOD.equals(expectedMethod)) { |
| assertEquals(POST_DATA, request.getUtf8Body()); |
| } |
| |
| MockResponse response = new MockResponse(); |
| response.setResponseCode(responseCode); |
| response.setBody(RESPONSE_CONTENT); |
| |
| log("Responding with: " + response); |
| return response; |
| } |
| |
| public RecordedRequest getLastRequest() { |
| return lastRequest; |
| } |
| |
| @Override |
| public MockResponse peek() { |
| return new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_END); |
| } |
| |
| private void log(String msg) { |
| HttpsURLConnectionTest.log("SingleRequestDispatcher", msg); |
| } |
| } |
| |
| /** |
| * Executes an HTTP request using the supplied connection. If {@code doOutput} is {@code true} |
| * the request made is a POST and the request body sent is {@link #POST_DATA}. |
| * If {@code doOutput} is {@code false} the request made is a GET. The response must be a |
| * success with a body {@link #RESPONSE_CONTENT}. |
| */ |
| private static void executeClientRequest( |
| HttpsURLConnection connection, boolean doOutput) throws IOException { |
| |
| // set up the connection |
| connection.setDoInput(true); |
| connection.setConnectTimeout(TIMEOUT); |
| connection.setReadTimeout(TIMEOUT); |
| connection.setDoOutput(doOutput); |
| |
| log("Client", "Opening the connection to " + connection.getURL()); |
| connection.connect(); |
| log("Client", "Connection has been ESTABLISHED, using proxy: " + connection.usingProxy()); |
| if (doOutput) { |
| log("Client", "Posting data"); |
| // connection configured to post data, do so |
| OutputStream os = connection.getOutputStream(); |
| os.write(POST_DATA.getBytes()); |
| } |
| // read the content of HTTP(s) response |
| InputStream is = connection.getInputStream(); |
| log("Client", "Input Stream obtained"); |
| byte[] buff = new byte[2048]; |
| int num = 0; |
| int byt; |
| while ((num < buff.length) && ((byt = is.read()) != -1)) { |
| buff[num++] = (byte) byt; |
| } |
| String message = new String(buff, 0, num); |
| log("Client", "Got content:\n" + message); |
| log("Client", "------------------"); |
| log("Client", "Response code: " + connection.getResponseCode()); |
| assertEquals(RESPONSE_CONTENT, message); |
| } |
| |
| /** |
| * Prints log message. |
| */ |
| public static synchronized void log(String origin, String message) { |
| if (DO_LOG) { |
| System.out.println("[" + origin + "]: " + message); |
| } |
| } |
| } |