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(); SSLContext ctx = SSLContext.getInstance("TLSv1"); ctx.init(keyManagers, trustManagers, null); return ctx; } /** * Sets up the properties pointing to the key store and trust store * and used as default values by JSSE staff. This is needed to test * HTTPS behaviour in the case of default SSL Socket Factories. */ private static void setUpStoreProperties() throws Exception { String type = KeyStore.getDefaultType(); System.setProperty("javax.net.ssl.keyStoreType", type); System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName()); System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD); System.setProperty("javax.net.ssl.trustStoreType", type); System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName()); System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD); } /** * Performs interaction between client's HttpsURLConnection and * servers side (ServerSocket). */ public static Socket doInteraction(final HttpsURLConnection clientConnection, final ServerSocket serverSocket) throws Throwable { return doInteraction(clientConnection, serverSocket, OK_CODE, false); } /** * Performs interaction between client's HttpsURLConnection and * servers side (ServerSocket). Server will response with specified * response code. */ public static Socket doInteraction(final HttpsURLConnection clientConnection, final ServerSocket serverSocket, final int responseCode) throws Throwable { return doInteraction(clientConnection, serverSocket, responseCode, false); } /** * Performs interaction between client's HttpsURLConnection and * servers side (ServerSocket). Server will response with specified * response code. * @param doAuthentication specifies * if the server needs client authentication. */ public static Socket doInteraction(final HttpsURLConnection clientConnection, final ServerSocket serverSocket, final int responseCode, final boolean doAuthentication) throws Throwable { // set up the connection clientConnection.setDoInput(true); clientConnection.setConnectTimeout(TIMEOUT); clientConnection.setReadTimeout(TIMEOUT); ServerWork server = new ServerWork(serverSocket, responseCode, doAuthentication); ClientConnectionWork client = new ClientConnectionWork(clientConnection); ExecutorService executorService = Executors.newFixedThreadPool(2); try { Future<Void> serverFuture = executorService.submit(server); Future<Void> clientFuture = executorService.submit(client); Throwable t = null; try { serverFuture.get(30, TimeUnit.SECONDS); } catch (ExecutionException e) { t = e.getCause(); } try { clientFuture.get(30, TimeUnit.SECONDS); } catch (ExecutionException e) { // two problems? log the first before overwriting if (t != null) { t.printStackTrace(); } t = e.getCause(); } if (t != null) { throw t; } } catch (ExecutionException e) { throw e.getCause(); } finally { executorService.shutdown(); } return server.peerSocket; } /** * The host name verifier used in test. */ static class TestHostnameVerifier implements HostnameVerifier { boolean verified = false; public boolean verify(String hostname, SSLSession session) { if (DO_LOG) { System.out.println("***> verification " + hostname + " " + session.getPeerHost()); } verified = true; return true; } } /** * The base class for mock Client and Server. */ static class Work { /** * The header of OK HTTP response. */ static final String responseHead = "HTTP/1.1 200 OK\r\n"; /** * The response message to be sent to the proxy CONNECT request. */ static final String proxyResponse = responseHead + "\r\n"; /** * The content of the response to be sent during HTTPS session. */ static final String httpsResponseContent = "<HTML>\n" + "<HEAD><TITLE>HTTPS Response Content</TITLE></HEAD>\n" + "</HTML>"; /** * The tail of the response to be sent during HTTPS session. */ static final String httpsResponseTail = "Content-type: text/html\r\n" + "Content-length: " + httpsResponseContent.length() + "\r\n" + "\r\n" + httpsResponseContent; /** * The response requiring client's proxy authentication. */ static final String respAuthenticationRequired = "HTTP/1.0 407 Proxy authentication required\r\n" + "Proxy-authenticate: Basic realm=\"localhost\"\r\n" + "\r\n"; /** * The data to be posted by client to the server. */ static final String clientsData = "_.-^ Client's Data ^-._"; /** * The print stream used for debug log. * If it is null debug info will not be printed. */ private PrintStream out = System.out; /** * Prints log message. */ public synchronized void log(String message) { if (DO_LOG && (out != null)) { out.println("[" + this + "]: " + message); } } } /** * The class used for server side works. */ static class ServerWork extends Work implements Callable<Void> { // the server socket used for connection private final ServerSocket serverSocket; // indicates if the server acts as proxy server private final boolean actAsProxy; // indicates if the server needs proxy authentication private final boolean needProxyAuthentication; // response code to be send to the client peer private final int responseCode; // the socket connected with client peer private Socket peerSocket; /** * Creates the thread acting as a server side. * @param serverSocket the server socket to be used during connection * @param responseCode the response code to be sent to the client * @param needProxyAuthentication * indicates if the server needs proxy authentication */ public ServerWork(ServerSocket serverSocket, int responseCode, boolean needProxyAuthentication) { this.serverSocket = serverSocket; this.responseCode = responseCode; this.needProxyAuthentication = needProxyAuthentication; // will act as a proxy server if the specified server socket // is not a secure server socket this.actAsProxy = !(serverSocket instanceof SSLServerSocket); if (!actAsProxy) { // demand client to send its certificate ((SSLServerSocket) serverSocket).setNeedClientAuth(true); } } /** * Closes the connection. */ public void closeSocket(Socket socket) { if (socket == null) { return; } try { socket.getInputStream().close(); } catch (IOException e) {} try { socket.getOutputStream().close(); } catch (IOException e) {} try { socket.close(); } catch (IOException e) {} } /** * Performs the actual server work. * If some exception occurs during the work it will be * stored in the <code>thrown</code> field. */ public Void call() throws Exception { // the buffer used for reading the messages byte[] buff = new byte[2048]; // the number of bytes read into the buffer try { // configure the server socket to avoid blocking serverSocket.setSoTimeout(TIMEOUT); // accept client connection peerSocket = serverSocket.accept(); // configure the client connection to avoid blocking peerSocket.setSoTimeout(TIMEOUT); log("Client connection ACCEPTED"); InputStream is = peerSocket.getInputStream(); OutputStream os = peerSocket.getOutputStream(); int num = is.read(buff); if (num == -1) { log("Unexpected EOF"); return null; } String message = new String(buff, 0, num); log("Got request:\n" + message); log("------------------"); if (!actAsProxy) { // Act as Server (not Proxy) side if (message.startsWith("POST")) { // client connection sent some data log("try to read client data"); String data = message.substring(message.indexOf("\r\n\r\n")+4); int dataNum = is.read(buff); if (dataNum != -1) { data += new String(buff, 0, dataNum); } 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 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); } 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); int dataNum = is.read(buff); if (dataNum != -1) { data += new String(buff, 0, dataNum); } 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