SSLSocketTestpublic class SSLSocketTest extends TestCase SSL integration tests that hit real servers. |
Fields Summary |
---|
private static SSLSocketFactory | clientFactory | private int | multithreadedFetchRuns | private int | multithreadedFetchWins | private Random | multithreadedFetchRandom | private static final String | SERVER_KEYS_JKSDefines the keystore contents for the server, JKS version. Holds just a
single self-generated key. The subject name is "Test Server". | private static final String | SERVER_KEYS_BKSDefines the keystore contents for the server, BKS version. Holds just a
single self-generated key. The subject name is "Test Server". | private static final String | CLIENT_KEYS_JKSDefines the keystore contents for the client, JKS version. Holds just a
single self-generated key. The subject name is "Test Client". | private static final String | CLIENT_KEYS_BKSDefines the keystore contents for the client, BKS version. Holds just a
single self-generated key. The subject name is "Test Client". | private static final String | PASSWORDDefines the password for the keystore. | private SSLSocket | handshakeSocket | private Exception | handshakeException |
Methods Summary |
---|
private static void | deleteDir(java.io.File directory)
if (!directory.exists()) {
return;
}
for (File file : directory.listFiles()) {
file.delete();
}
directory.delete();
| private void | fetch(javax.net.ssl.SSLSocketFactory socketFactory, java.lang.String host, int port, boolean secure, java.lang.String path, int outerLoop, int innerLoop, int delay, int timeout)Does a number of HTTPS requests on some host and consumes the response.
We don't use the HttpsUrlConnection class, but do this on our own
with the SSLSocket class. This gives us a chance to test the basic
behavior of SSL.
InetSocketAddress address = new InetSocketAddress(host, port);
for (int i = 0; i < outerLoop; i++) {
// Connect to the remote host
Socket socket = secure ? socketFactory.createSocket()
: new Socket();
if (timeout >= 0) {
socket.setKeepAlive(true);
socket.setSoTimeout(timeout * 1000);
}
socket.connect(address);
// Get the streams
OutputStream output = socket.getOutputStream();
PrintWriter writer = new PrintWriter(output);
try {
DataInputStream input = new DataInputStream(socket.getInputStream());
try {
for (int j = 0; j < innerLoop; j++) {
android.util.Log.d("SSLSocketTest",
"GET https://" + host + path + " HTTP/1.1");
// Send a request
writer.println("GET https://" + host + path + " HTTP/1.1\r");
writer.println("Host: " + host + "\r");
writer.println("Connection: " +
(j == innerLoop - 1 ? "Close" : "Keep-Alive")
+ "\r");
writer.println("\r");
writer.flush();
int length = -1;
boolean chunked = false;
String line = input.readLine();
if (line == null) {
throw new IOException("No response from server");
// android.util.Log.d("SSLSocketTest", "No response from server");
}
// Consume the headers, check content length and encoding type
while (line != null && line.length() != 0) {
// System.out.println(line);
int dot = line.indexOf(':");
if (dot != -1) {
String key = line.substring(0, dot).trim();
String value = line.substring(dot + 1).trim();
if ("Content-Length".equalsIgnoreCase(key)) {
length = Integer.valueOf(value);
} else if ("Transfer-Encoding".equalsIgnoreCase(key)) {
chunked = "Chunked".equalsIgnoreCase(value);
}
}
line = input.readLine();
}
assertTrue("Need either content length or chunked encoding", length != -1
|| chunked);
// Consume the content itself
if (chunked) {
length = Integer.parseInt(input.readLine(), 16);
while (length != 0) {
byte[] buffer = new byte[length];
input.readFully(buffer);
input.readLine();
length = Integer.parseInt(input.readLine(), 16);
}
input.readLine();
} else {
byte[] buffer = new byte[length];
input.readFully(buffer);
}
// Sleep for the given number of seconds
try {
Thread.sleep(delay * 1000);
} catch (InterruptedException ex) {
// Shut up!
}
}
} finally {
input.close();
}
} finally {
writer.close();
}
// Close the connection
socket.close();
}
| private void | fetch(java.lang.String host, int port, boolean secure, java.lang.String path, int outerLoop, int innerLoop, int delay, int timeout)Invokes fetch() with the default socket factory.
fetch(clientFactory, host, port, secure, path, outerLoop, innerLoop,
delay, timeout);
| private javax.net.ssl.KeyManager[] | getKeyManagers(java.lang.String keys)Loads a keystore from a base64-encoded String. Returns the KeyManager[]
for the result.
byte[] bytes = new Base64().decode(keys.getBytes());
InputStream inputStream = new ByteArrayInputStream(bytes);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(inputStream, PASSWORD.toCharArray());
inputStream.close();
String algorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
keyManagerFactory.init(keyStore, PASSWORD.toCharArray());
return keyManagerFactory.getKeyManagers();
| public static void | main(java.lang.String[] args)
new SSLSocketTest().testFileBasedClientSessionCache();
| private void | makeRequests(javax.net.ssl.SSLSocketFactory socketFactory)Executes sequence of requests twice using given socket factory.
for (int i = 0; i < 2; i++) {
fetch(socketFactory, "www.fortify.net", 443, true, "/sslcheck.html",
1, 1, 0, 60);
fetch(socketFactory, "www.paypal.com", 443, true, "/",
1, 1, 0, 60);
fetch(socketFactory, "www.yellownet.ch", 443, true, "/",
1, 1, 0, 60);
}
| public void | testClientAuth()Implements the actual test case. Launches a server and a client, requires
client authentication and checks the certificates afterwards (not in the
usual sense, we just make sure that we got the expected certificates,
because our self-signed test certificates are not valid.)
try {
TestServer server = new TestServer(8088, true, TestServer.CLIENT_AUTH_WANTED);
TestClient client = new TestClient(8088, true);
Thread serverThread = new Thread(server);
Thread clientThread = new Thread(client);
serverThread.start();
clientThread.start();
serverThread.join();
clientThread.join();
// The server must have completed without an exception.
if (server.getException() != null) {
throw new RuntimeException(server.getException());
}
// The client must have completed without an exception.
if (client.getException() != null) {
throw new RuntimeException(client.getException());
}
// Caution: The clientChain is the certificate chain from our
// client object. It contains the server certificates, of course!
X509Certificate[] clientChain = client.getChain();
assertTrue("Client cert chain must not be null", clientChain != null);
assertTrue("Client cert chain must not be empty", clientChain.length != 0);
assertEquals("CN=Test Server, OU=Android, O=Google, L=MTV, ST=California, C=US", clientChain[0].getSubjectDN().toString());
// Important part ------^
// Caution: The serverChain is the certificate chain from our
// server object. It contains the client certificates, of course!
X509Certificate[] serverChain = server.getChain();
assertTrue("Server cert chain must not be null", serverChain != null);
assertTrue("Server cert chain must not be empty", serverChain.length != 0);
assertEquals("CN=Test Client, OU=Android, O=Google, L=MTV, ST=California, C=US", serverChain[0].getSubjectDN().toString());
// Important part ------^
} catch (Exception ex) {
throw new RuntimeException(ex);
}
| public void | testClientSessionCaching()Tests our in-memory and persistent caching support.
SSLContextImpl context = new SSLContextImpl();
// Cache size = 2.
FakeClientSessionCache fakeCache = new FakeClientSessionCache();
context.engineInit(null, null, null, fakeCache, null);
SSLSocketFactory socketFactory = context.engineGetSocketFactory();
context.engineGetClientSessionContext().setSessionCacheSize(2);
makeRequests(socketFactory);
List<String> smallCacheOps = Arrays.asList(
"get www.fortify.net",
"put www.fortify.net",
"get www.paypal.com",
"put www.paypal.com",
"get www.yellownet.ch",
"put www.yellownet.ch",
// At this point, all in-memory cache requests should miss,
// but the sessions will still be in the persistent cache.
"get www.fortify.net",
"get www.paypal.com",
"get www.yellownet.ch"
);
assertEquals(smallCacheOps, fakeCache.ops);
// Cache size = 3.
fakeCache = new FakeClientSessionCache();
context.engineInit(null, null, null, fakeCache, null);
socketFactory = context.engineGetSocketFactory();
context.engineGetClientSessionContext().setSessionCacheSize(3);
makeRequests(socketFactory);
List<String> bigCacheOps = Arrays.asList(
"get www.fortify.net",
"put www.fortify.net",
"get www.paypal.com",
"put www.paypal.com",
"get www.yellownet.ch",
"put www.yellownet.ch"
// At this point, all results should be in the in-memory
// cache, and the persistent cache shouldn't be hit anymore.
);
assertEquals(bigCacheOps, fakeCache.ops);
// Cache size = 4.
fakeCache = new FakeClientSessionCache();
context.engineInit(null, null, null, fakeCache, null);
socketFactory = context.engineGetSocketFactory();
context.engineGetClientSessionContext().setSessionCacheSize(4);
makeRequests(socketFactory);
assertEquals(bigCacheOps, fakeCache.ops);
| public void | testContextInitNullArgs()Regression test for 865926: SSLContext.init() should
use default values for null arguments.
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, null, null);
| public void | testDefaultAlgorithms()Regression test for 963650: javax.net.ssl.KeyManager has no implemented
(documented?) algorithms.
SSLContext ctx = SSLContext.getInstance("TLS");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
KeyStore ks = KeyStore.getInstance("BKS");
assertEquals("X509", kmf.getAlgorithm());
assertEquals("X509", KeyManagerFactory.getDefaultAlgorithm());
assertEquals("BKS", ks.getType());
assertEquals("BKS", KeyStore.getDefaultType());
| public void | testFileBasedClientSessionCache()
SSLContextImpl context = new SSLContextImpl();
String tmpDir = System.getProperty("java.io.tmpdir");
if (tmpDir == null) {
fail("Please set 'java.io.tmpdir' system property.");
}
File cacheDir = new File(tmpDir
+ "/" + SSLSocketTest.class.getName() + "/cache");
deleteDir(cacheDir);
SSLClientSessionCache fileCache
= FileClientSessionCache.usingDirectory(cacheDir);
try {
ClientSessionCacheProxy cacheProxy
= new ClientSessionCacheProxy(fileCache);
context.engineInit(null, null, null, cacheProxy, null);
SSLSocketFactory socketFactory = context.engineGetSocketFactory();
context.engineGetClientSessionContext().setSessionCacheSize(1);
makeRequests(socketFactory);
List<String> expected = Arrays.asList(
"unsuccessful get www.fortify.net",
"put www.fortify.net",
"unsuccessful get www.paypal.com",
"put www.paypal.com",
"unsuccessful get www.yellownet.ch",
"put www.yellownet.ch",
// At this point, all in-memory cache requests should miss,
// but the sessions will still be in the persistent cache.
"successful get www.fortify.net",
"successful get www.paypal.com",
"successful get www.yellownet.ch"
);
assertEquals(expected, cacheProxy.ops);
// Try again now that file-based cache is populated.
fileCache = FileClientSessionCache.usingDirectory(cacheDir);
cacheProxy = new ClientSessionCacheProxy(fileCache);
context.engineInit(null, null, null, cacheProxy, null);
socketFactory = context.engineGetSocketFactory();
context.engineGetClientSessionContext().setSessionCacheSize(1);
makeRequests(socketFactory);
expected = Arrays.asList(
"successful get www.fortify.net",
"successful get www.paypal.com",
"successful get www.yellownet.ch",
"successful get www.fortify.net",
"successful get www.paypal.com",
"successful get www.yellownet.ch"
);
assertEquals(expected, cacheProxy.ops);
} finally {
deleteDir(cacheDir);
}
| public void | testLongTimeout()Does repeated requests for each of the hosts, with the connection being
kept alive in between. Waits a longer time after each request.
Expectation is that the host closes the connection.
// Seems to have a veeeery long timeout.
// fetch("www.fortify.net", 443, "/sslcheck.html", 1, 2, 60);
// Google has a 60s timeout, so 90s of waiting should trigger it.
try {
fetch("mail.google.com", 443, true, "/mail/", 1, 2, 90, 180);
fail("Oops - timeout expected.");
} catch (IOException ex) {
// Expected.
}
// These two don't accept keep-alive
// fetch("www.paypal.com", 443, "/", 1, 10);
// fetch("www.yellownet.ch", 443, "/", 1, 10);
| public void | testMultithreadedClose()Regression test for problem where close() resulted in a hand if
a different thread was sitting in a blocking read or write.
InetSocketAddress address = new InetSocketAddress("www.fortify.net", 443);
final Socket socket = clientFactory.createSocket();
socket.connect(address);
Thread reader = new Thread() {
@Override
public void run() {
try {
byte[] buffer = new byte[512];
InputStream stream = socket.getInputStream();
socket.getInputStream().read(buffer);
} catch (Exception ex) {
android.util.Log.d("SSLSocketTest",
"testMultithreadedClose() reader got " + ex.toString());
}
}
};
Thread closer = new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
socket.close();
} catch (Exception ex) {
android.util.Log.d("SSLSocketTest",
"testMultithreadedClose() closer got " + ex.toString());
}
}
};
android.util.Log.d("SSLSocketTest", "testMultithreadedClose() starting reader...");
reader.start();
android.util.Log.d("SSLSocketTest", "testMultithreadedClose() starting closer...");
closer.start();
long t1 = System.currentTimeMillis();
android.util.Log.d("SSLSocketTest", "testMultithreadedClose() joining reader...");
reader.join(30000);
android.util.Log.d("SSLSocketTest", "testMultithreadedClose() joining closer...");
closer.join(30000);
long t2 = System.currentTimeMillis();
assertTrue("Concurrent close() hangs", t2 - t1 < 30000);
| public void | testMultithreadedFetch()Regression test for problem where multiple threads with multiple SSL
connection would cause problems due to either missing native locking
or the slowness of the SSL connections.
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
multithreadedFetchRuns++;
switch (multithreadedFetchRandom.nextInt(4)) {
case 0: {
fetch("www.fortify.net", 443,
true, "/sslcheck.html", 1, 1, 0, 60);
break;
}
case 1: {
fetch("mail.google.com", 443, true, "/mail/", 1, 1, 0, 60);
break;
}
case 2: {
fetch("www.paypal.com", 443, true, "/", 1, 1, 0, 60);
break;
}
case 3: {
fetch("www.yellownet.ch", 443, true, "/", 1, 1, 0, 60);
break;
}
}
multithreadedFetchWins++;
} catch (Exception ex) {
android.util.Log.d("SSLSocketTest",
"testMultithreadedFetch() got Exception", ex);
}
}
}
};
threads[i].start();
android.util.Log.d("SSLSocketTest", "testMultithreadedFetch() started thread #" + i);
}
for (int i = 0; i < threads.length; i++) {
try {
threads[i].join();
android.util.Log.d("SSLSocketTest", "testMultithreadedFetch() joined thread #" + i);
} catch (InterruptedException ex) {
// Not interested.
}
}
assertTrue("At least 95% of multithreaded SSL connections must succeed",
multithreadedFetchWins >= (multithreadedFetchRuns * 95) / 100);
| public void | testRepeatedClose()Does repeated requests for each of the hosts, with the connection being
closed in between.
fetch("www.fortify.net", 443, true, "/sslcheck.html", 10, 1, 0, 60);
fetch("mail.google.com", 443, true, "/mail/", 10, 1, 0, 60);
fetch("www.paypal.com", 443, true, "/", 10, 1, 0, 60);
fetch("www.yellownet.ch", 443, true, "/", 10, 1, 0, 60);
| public void | testRepeatedKeepAlive()Does repeated requests for each of the hosts, with the connection being
kept alive in between.
fetch("www.fortify.net", 443, true, "/sslcheck.html", 1, 10, 0, 60);
fetch("mail.google.com", 443, true, "/mail/", 1, 10, 0, 60);
// These two don't accept keep-alive
// fetch("www.paypal.com", 443, "/", 1, 10);
// fetch("www.yellownet.ch", 443, "/", 1, 10);
| public void | testSSLHandshakeHangClose()
Thread thread = new Thread() {
@Override
public void run() {
try {
handshakeSocket = (SSLSocket)clientFactory.createSocket(
"www.heise.de", 80);
handshakeSocket.startHandshake();
} catch (Exception ex) {
handshakeException = ex;
}
}
};
thread.start();
try {
Thread.sleep(5000);
try {
handshakeSocket.close();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
thread.join(5000);
} catch (InterruptedException ex) {
// Ignore.
}
if (handshakeException == null) {
fail("SSL handshake should have failed.");
}
| public void | testSSLHandshakeHangTimeout()
Thread thread = new Thread() {
@Override
public void run() {
try {
SSLSocket socket = (SSLSocket)clientFactory.createSocket(
"www.heise.de", 80);
socket.setSoTimeout(5000);
socket.startHandshake();
socket.close();
} catch (Exception ex) {
handshakeException = ex;
}
}
};
thread.start();
try {
thread.join(10000);
} catch (InterruptedException ex) {
// Ignore.
}
if (handshakeException == null) {
fail("SSL handshake should have failed.");
}
| public void | testShortTimeout()Does repeated requests for each of the hosts, with the connection being
closed in between. Waits a couple of seconds after each request, but
stays within a reasonable timeout. Expectation is that the connection
stays open.
fetch("www.fortify.net", 443, true, "/sslcheck.html", 1, 10, 5, 60);
fetch("mail.google.com", 443, true, "/mail/", 1, 10, 5, 60);
// These two don't accept keep-alive
// fetch("www.paypal.com", 443, "/", 1, 10);
// fetch("www.yellownet.ch", 443, "/", 1, 10);
| public void | testSimple()Does a single request for each of the hosts. Consumes the response.
fetch("www.fortify.net", 443, true, "/sslcheck.html", 1, 1, 0, 60);
fetch("mail.google.com", 443, true, "/mail/", 1, 1, 0, 60);
fetch("www.paypal.com", 443, true, "/", 1, 1, 0, 60);
fetch("www.yellownet.ch", 443, true, "/", 1, 1, 0, 60);
| public void | xxtestBrokenConnection()Does repeated requests for each of the hosts, with the connection being
closed in between. Waits a longer time after each request. Expectation is
that the host closes the connection.
try {
fetch("www.fortify.net", 443, true, "/sslcheck.html", 1, 2, 60, 60);
fail("Oops - timeout expected.");
} catch (IOException ex) {
android.util.Log.d("SSLSocketTest", "Exception", ex);
// Expected.
}
// These two don't accept keep-alive
// fetch("www.paypal.com", 443, "/", 1, 10);
// fetch("www.yellownet.ch", 443, "/", 1, 10);
| public void | xxtestBrokenConnection2()Does repeated requests for each of the hosts, with the connection being
closed in between. Waits a longer time after each request. Expectation is
that the host closes the connection.
try {
fetch("www.heise.de", 80, false, "/index.html", 1, 2, 60, 60);
fail("Oops - timeout expected.");
} catch (IOException ex) {
android.util.Log.d("SSLSocketTest", "Exception", ex);
// Expected.
}
// These two don't accept keep-alive
// fetch("www.paypal.com", 443, "/", 1, 10);
// fetch("www.yellownet.ch", 443, "/", 1, 10);
|
|