blob: 4c277bba8a997698fb24109710add1db123b7bc1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.harmony.luni.tests.internal.net.www.protocol.https;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import junit.framework.TestCase;
import libcore.java.security.TestKeyStore;
/**
* Implementation independent test for HttpsURLConnection.
* The test needs certstore file placed in system classpath
* and named as "key_store." + the type of the
* default KeyStore installed in the system in lower case.
* <br>
* For example: if default KeyStore type in the system is BKS
* (i.e. java.security file sets up the property keystore.type=BKS),
* thus classpath should point to the directory with "key_store.bks"
* file.
* <br>
* This certstore file should contain self-signed certificate
* generated by keytool utility in a usual way.
* <br>
* The password to the certstore should be "password" (without quotes).
*/
public class HttpsURLConnectionTest extends TestCase {
// the password to the store
private static final String KS_PASSWORD = "password";
// turn on/off logging
private static final boolean DO_LOG = false;
// read/connection timeout value
private static final int TIMEOUT = 5000;
// OK response code
private static final int OK_CODE = 200;
// Not Found response code
private static final int NOT_FOUND_CODE = 404;
// Proxy authentication required response code
private static final int AUTHENTICATION_REQUIRED_CODE = 407;
private static File store;
static {
try {
store = File.createTempFile("key_store", "bks");
} catch (Exception e) {
// ignore
}
}
/**
* Checks that HttpsURLConnection's default SSLSocketFactory is operable.
*/
public void testGetDefaultSSLSocketFactory() throws Exception {
// set up the properties defining the default values needed by SSL stuff
setUpStoreProperties();
SSLSocketFactory defaultSSLSF = HttpsURLConnection.getDefaultSSLSocketFactory();
ServerSocket ss = new ServerSocket(0);
Socket s = defaultSSLSF.createSocket("localhost", ss.getLocalPort());
ss.accept();
s.close();
ss.close();
}
public void testHttpsConnection() throws Throwable {
// set up the properties defining the default values needed by SSL stuff
setUpStoreProperties();
// create the SSL server socket acting as a server
SSLContext ctx = getContext();
ServerSocket ss = ctx.getServerSocketFactory().createServerSocket(0);
// create the HostnameVerifier to check hostname verification
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create url connection to be tested
URL url = new URL("https://localhost:" + ss.getLocalPort());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(ctx.getSocketFactory());
// perform the interaction between the peers
SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
// check the connection state
checkConnectionStateParameters(connection, peerSocket);
// should silently exit
connection.connect();
}
/**
* Tests the behaviour of HTTPS connection in case of unavailability
* of requested resource.
*/
public void testHttpsConnection_Not_Found_Response() throws Throwable {
// set up the properties defining the default values needed by SSL stuff
setUpStoreProperties();
// create the SSL server socket acting as a server
SSLContext ctx = getContext();
ServerSocket ss = ctx.getServerSocketFactory().createServerSocket(0);
// create the HostnameVerifier to check hostname verification
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create url connection to be tested
URL url = new URL("https://localhost:" + ss.getLocalPort());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(ctx.getSocketFactory());
try {
doInteraction(connection, ss, NOT_FOUND_CODE);
fail("Expected exception was not thrown.");
} catch (FileNotFoundException e) {
if (DO_LOG) {
System.out.println("Expected exception was thrown: " + e.getMessage());
e.printStackTrace();
}
}
// should silently exit
connection.connect();
}
/**
* Tests possibility to set up the default SSLSocketFactory
* to be used by HttpsURLConnection.
*/
public void testSetDefaultSSLSocketFactory() throws Throwable {
// create the SSLServerSocket which will be used by server side
SSLContext ctx = getContext();
SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(0);
SSLSocketFactory socketFactory = (SSLSocketFactory) ctx.getSocketFactory();
// set up the factory as default
HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);
// check the result
assertSame("Default SSLSocketFactory differs from expected",
socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory());
// create the HostnameVerifier to check hostname verification
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create HttpsURLConnection to be tested
URL url = new URL("https://localhost:" + ss.getLocalPort());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
TestHostnameVerifier hnv_late = new TestHostnameVerifier();
// late initialization: should not be used for created connection
HttpsURLConnection.setDefaultHostnameVerifier(hnv_late);
// perform the interaction between the peers
SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
// check the connection state
checkConnectionStateParameters(connection, peerSocket);
// check the verification process
assertTrue("Hostname verification was not done", hnv.verified);
assertFalse("Hostname verification should not be done by this verifier",
hnv_late.verified);
// check the used SSLSocketFactory
assertSame("Default SSLSocketFactory should be used",
HttpsURLConnection.getDefaultSSLSocketFactory(),
connection.getSSLSocketFactory());
// should silently exit
connection.connect();
}
/**
* Tests possibility to set up the SSLSocketFactory
* to be used by HttpsURLConnection.
*/
public void testSetSSLSocketFactory() throws Throwable {
// create the SSLServerSocket which will be used by server side
SSLContext ctx = getContext();
SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(0);
// create the HostnameVerifier to check hostname verification
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create HttpsURLConnection to be tested
URL url = new URL("https://localhost:" + ss.getLocalPort());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
SSLSocketFactory socketFactory = (SSLSocketFactory) ctx.getSocketFactory();
connection.setSSLSocketFactory(socketFactory);
TestHostnameVerifier hnv_late = new TestHostnameVerifier();
// late initialization: should not be used for created connection
HttpsURLConnection.setDefaultHostnameVerifier(hnv_late);
// perform the interaction between the peers
SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
// check the connection state
checkConnectionStateParameters(connection, peerSocket);
// check the verification process
assertTrue("Hostname verification was not done", hnv.verified);
assertFalse("Hostname verification should not be done by this verifier",
hnv_late.verified);
// check the used SSLSocketFactory
assertNotSame("Default SSLSocketFactory should not be used",
HttpsURLConnection.getDefaultSSLSocketFactory(),
connection.getSSLSocketFactory());
assertSame("Result differs from expected",
socketFactory, connection.getSSLSocketFactory());
// should silently exit
connection.connect();
}
/**
* Tests the behaviour of HttpsURLConnection in case of retrieving
* of the connection state parameters before connection has been made.
*/
public void testUnconnectedStateParameters() throws Throwable {
// create HttpsURLConnection to be tested
URL url = new URL("https://localhost:55555");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
try {
connection.getCipherSuite();
fail("Expected IllegalStateException was not thrown");
} catch (IllegalStateException e) {}
try {
connection.getPeerPrincipal();
fail("Expected IllegalStateException was not thrown");
} catch (IllegalStateException e) {}
try {
connection.getLocalPrincipal();
fail("Expected IllegalStateException was not thrown");
} catch (IllegalStateException e) {}
try {
connection.getServerCertificates();
fail("Expected IllegalStateException was not thrown");
} catch (IllegalStateException e) {}
try {
connection.getLocalCertificates();
fail("Expected IllegalStateException was not thrown");
} catch (IllegalStateException e) {}
}
/**
* Tests if setHostnameVerifier() method replaces default verifier.
*/
public void testSetHostnameVerifier() throws Throwable {
// setting up the properties pointing to the key/trust stores
setUpStoreProperties();
// create the SSLServerSocket which will be used by server side
SSLServerSocket ss = (SSLServerSocket)
getContext().getServerSocketFactory().createServerSocket(0);
// create the HostnameVerifier to check that Hostname verification
// is done
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create HttpsURLConnection to be tested
URL url = new URL("https://localhost:" + ss.getLocalPort());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(getContext().getSocketFactory());
TestHostnameVerifier hnv_late = new TestHostnameVerifier();
// replace default verifier
connection.setHostnameVerifier(hnv_late);
// perform the interaction between the peers and check the results
SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
assertTrue("Hostname verification was not done", hnv_late.verified);
assertFalse("Hostname verification should not be done by this verifier",
hnv.verified);
checkConnectionStateParameters(connection, peerSocket);
// should silently exit
connection.connect();
}
/**
* Tests the behaviour in case of sending the data to the server.
*/
public void test_doOutput() throws Throwable {
// setting up the properties pointing to the key/trust stores
setUpStoreProperties();
// create the SSLServerSocket which will be used by server side
SSLServerSocket ss = (SSLServerSocket)
getContext().getServerSocketFactory().createServerSocket(0);
// create the HostnameVerifier to check that Hostname verification
// is done
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create HttpsURLConnection to be tested
URL url = new URL("https://localhost:" + ss.getLocalPort());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(getContext().getSocketFactory());
connection.setDoOutput(true);
// perform the interaction between the peers and check the results
SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
checkConnectionStateParameters(connection, peerSocket);
// should silently exit
connection.connect();
}
/**
* Tests HTTPS connection process made through the proxy server.
*/
public void testProxyConnection() throws Throwable {
// setting up the properties pointing to the key/trust stores
setUpStoreProperties();
// create the SSLServerSocket which will be used by server side
ServerSocket ss = new ServerSocket(0);
// create the HostnameVerifier to check that Hostname verification
// is done
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create HttpsURLConnection to be tested
URL url = new URL("https://requested.host:55556/requested.data");
HttpsURLConnection connection = (HttpsURLConnection)
url.openConnection(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("localhost",
ss.getLocalPort())));
connection.setSSLSocketFactory(getContext().getSocketFactory());
// perform the interaction between the peers and check the results
SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
checkConnectionStateParameters(connection, peerSocket);
// should silently exit
connection.connect();
}
/**
* Tests HTTPS connection process made through the proxy server.
* Proxy server needs authentication.
*/
public void testProxyAuthConnection() throws Throwable {
// setting up the properties pointing to the key/trust stores
setUpStoreProperties();
// create the SSLServerSocket which will be used by server side
ServerSocket ss = new ServerSocket(0);
// create the HostnameVerifier to check that Hostname verification
// is done
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("user", "password"
.toCharArray());
}
});
// create HttpsURLConnection to be tested
URL url = new URL("https://requested.host:55555/requested.data");
HttpsURLConnection connection = (HttpsURLConnection)
url.openConnection(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("localhost",
ss.getLocalPort())));
connection.setSSLSocketFactory(getContext().getSocketFactory());
// perform the interaction between the peers and check the results
SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
checkConnectionStateParameters(connection, peerSocket);
// should silently exit
connection.connect();
}
/**
* Tests HTTPS connection process made through the proxy server.
* 2 HTTPS connections are opened for one URL. For the first time
* the connection is opened through one proxy,
* for the second time through another.
*/
public void testConsequentProxyConnection() throws Throwable {
// setting up the properties pointing to the key/trust stores
setUpStoreProperties();
// create the SSLServerSocket which will be used by server side
ServerSocket ss = new ServerSocket(0);
// create the HostnameVerifier to check that Hostname verification
// is done
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create HttpsURLConnection to be tested
URL url = new URL("https://requested.host:55555/requested.data");
HttpsURLConnection connection = (HttpsURLConnection)
url.openConnection(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("localhost",
ss.getLocalPort())));
connection.setSSLSocketFactory(getContext().getSocketFactory());
// perform the interaction between the peers and check the results
SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
checkConnectionStateParameters(connection, peerSocket);
// create another SSLServerSocket which will be used by server side
ss = new ServerSocket(0);
connection = (HttpsURLConnection) url.openConnection(new Proxy(
Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort())));
connection.setSSLSocketFactory(getContext().getSocketFactory());
// perform the interaction between the peers and check the results
peerSocket = (SSLSocket) doInteraction(connection, ss);
checkConnectionStateParameters(connection, peerSocket);
}
/**
* Tests HTTPS connection process made through the proxy server.
* Proxy server needs authentication.
* Client sends data to the server.
*/
public void testProxyAuthConnection_doOutput() throws Throwable {
// setting up the properties pointing to the key/trust stores
setUpStoreProperties();
// create the SSLServerSocket which will be used by server side
ServerSocket ss = new ServerSocket(0);
// create the HostnameVerifier to check that Hostname verification
// is done
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("user", "password"
.toCharArray());
}
});
// create HttpsURLConnection to be tested
URL url = new URL("https://requested.host:55554/requested.data");
HttpsURLConnection connection = (HttpsURLConnection)
url.openConnection(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("localhost",
ss.getLocalPort())));
connection.setSSLSocketFactory(getContext().getSocketFactory());
connection.setDoOutput(true);
// perform the interaction between the peers and check the results
SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss, OK_CODE, true);
checkConnectionStateParameters(connection, peerSocket);
}
/**
* Tests HTTPS connection process made through the proxy server.
* Proxy server needs authentication but client fails to authenticate
* (Authenticator was not set up in the system).
*/
public void testProxyAuthConnectionFailed() throws Throwable {
// setting up the properties pointing to the key/trust stores
setUpStoreProperties();
// create the SSLServerSocket which will be used by server side
ServerSocket ss = new ServerSocket(0);
// create the HostnameVerifier to check that Hostname verification
// is done
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create HttpsURLConnection to be tested
URL url = new URL("https://requested.host:55555/requested.data");
HttpsURLConnection connection = (HttpsURLConnection)
url.openConnection(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("localhost",
ss.getLocalPort())));
connection.setSSLSocketFactory(getContext().getSocketFactory());
// perform the interaction between the peers and check the results
try {
doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE, true);
} catch (IOException e) {
// SSL Tunnelling failed
if (DO_LOG) {
System.out.println("Got expected IOException: "
+ e.getMessage());
}
}
}
/**
* Tests the behaviour of HTTPS connection in case of unavailability
* of requested resource.
*/
public void testProxyConnection_Not_Found_Response() throws Throwable {
// setting up the properties pointing to the key/trust stores
setUpStoreProperties();
// create the SSLServerSocket which will be used by server side
ServerSocket ss = new ServerSocket(0);
// create the HostnameVerifier to check that Hostname verification
// is done
TestHostnameVerifier hnv = new TestHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
// create HttpsURLConnection to be tested
URL url = new URL("https://localhost:" + ss.getLocalPort());
HttpsURLConnection connection = (HttpsURLConnection)
url.openConnection(new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("localhost",
ss.getLocalPort())));
connection.setSSLSocketFactory(getContext().getSocketFactory());
try {
doInteraction(connection, ss, NOT_FOUND_CODE); // NOT FOUND
fail("Expected exception was not thrown.");
} catch (FileNotFoundException e) {
if (DO_LOG) {
System.out.println("Expected exception was thrown: "
+ e.getMessage());
}
}
}
/**
* Log the name of the test case to be executed.
*/
public void setUp() throws Exception {
super.setUp();
if (DO_LOG) {
System.out.println();
System.out.println("------------------------");
System.out.println("------ " + getName());
System.out.println("------------------------");
}
if (store != null) {
String ksFileName = ("org/apache/harmony/luni/tests/key_store."
+ KeyStore.getDefaultType().toLowerCase());
InputStream in = getClass().getClassLoader().getResourceAsStream(ksFileName);
FileOutputStream out = new FileOutputStream(store);
BufferedInputStream bufIn = new BufferedInputStream(in, 8192);
while (bufIn.available() > 0) {
byte[] buf = new byte[128];
int read = bufIn.read(buf);
out.write(buf, 0, read);
}
bufIn.close();
out.close();
} else {
fail("couldn't set up key store");
}
}
public void tearDown() {
if (store != null) {
store.delete();
}
}
/**
* Checks the HttpsURLConnection getter's values and compares
* them with actual corresponding values of remote peer.
*/
public static void checkConnectionStateParameters(
HttpsURLConnection clientConnection, SSLSocket serverPeer)
throws Exception {
SSLSession session = serverPeer.getSession();
assertEquals(session.getCipherSuite(), clientConnection.getCipherSuite());
assertEquals(session.getLocalPrincipal(), clientConnection.getPeerPrincipal());
assertEquals(session.getPeerPrincipal(), clientConnection.getLocalPrincipal());
Certificate[] serverCertificates = clientConnection.getServerCertificates();
Certificate[] localCertificates = session.getLocalCertificates();
assertTrue("Server certificates differ from expected",
Arrays.equals(serverCertificates, localCertificates));
localCertificates = clientConnection.getLocalCertificates();
serverCertificates = session.getPeerCertificates();
assertTrue("Local certificates differ from expected",
Arrays.equals(serverCertificates, localCertificates));
}
/**
* Returns the file name of the key/trust store. The key store file
* (named as "key_store." + extension equals to the default KeyStore
* type installed in the system in lower case) is searched in classpath.
* @throws junit.framework.AssertionFailedError if property was not set
* or file does not exist.
*/
private static String getKeyStoreFileName() {
return store.getAbsolutePath();
}
/**
* Builds and returns the context used for secure socket creation.
*/
private static SSLContext getContext() throws Exception {
String type = KeyStore.getDefaultType();
String keyStore = getKeyStoreFileName();
File keyStoreFile = new File(keyStore);
FileInputStream fis = new FileInputStream(keyStoreFile);
KeyStore ks = KeyStore.getInstance(type);
ks.load(fis, KS_PASSWORD.toCharArray());
fis.close();
if (DO_LOG && false) {
TestKeyStore.dump("HttpsURLConnection.getContext", ks, KS_PASSWORD.toCharArray());
}
String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm);
kmf.init(ks, KS_PASSWORD.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();
String tmfAlgorthm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorthm);
tmf.init(ks);
TrustManager[] trustManagers = tmf.getTrustManagers();
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
* stored in the <code>thrown<code> field.
*/
public Void call() throws Exception {
log("Opening the connection to " + connection.getURL());
connection.connect();
log("Connection has been ESTABLISHED, using proxy: " + connection.usingProxy());
if (connection.getDoOutput()) {
log("Posting data");
// connection configured to post data, do so
connection.getOutputStream().write(clientsData.getBytes());
}
// read the content of HTTP(s) response
InputStream is = connection.getInputStream();
log("Input Stream obtained");
byte[] buff = new byte[2048];
int num = 0;
int byt = 0;
while ((num < buff.length) && ((byt = is.read()) != -1)) {
buff[num++] = (byte) byt;
}
String message = new String(buff, 0, num);
log("Got content:\n" + message);
log("------------------");
log("Response code: " + connection.getResponseCode());
assertEquals(httpsResponseContent, message);
return null;
}
@Override public String toString() {
return "Client Connection";
}
}
}