FileDocCategorySizeDatePackage
SSLSocketTest.javaAPI DocAndroid 1.5 API45969Wed May 06 22:42:02 BST 2009android.core

SSLSocketTest

public 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_JKS
Defines 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_BKS
Defines 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_JKS
Defines 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_BKS
Defines 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
PASSWORD
Defines the password for the keystore.
private SSLSocket
handshakeSocket
private Exception
handshakeException
Constructors Summary
Methods Summary
private static voiddeleteDir(java.io.File directory)

        if (!directory.exists()) {
            return;
        }
        for (File file : directory.listFiles()) {
            file.delete();
        }
        directory.delete();
    
private voidfetch(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.

param
host The host name the request is being sent to.
param
port The port the request is being sent to.
param
path The path being requested (e.g. "/index.html").
param
outerLoop The number of times we reconnect and do the request.
param
innerLoop The number of times we do the request for each connection (using HTTP keep-alive).
param
delay The delay after each request (in seconds).
throws
IOException When a problem occurs.


                                                                                                                                                              
           
                   
                  
        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 voidfetch(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 voidmain(java.lang.String[] args)

        new SSLSocketTest().testFileBasedClientSessionCache();
    
private voidmakeRequests(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 voidtestClientAuth()
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 voidtestClientSessionCaching()
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 voidtestContextInitNullArgs()
Regression test for 865926: SSLContext.init() should use default values for null arguments.

            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, null, null);
    
public voidtestDefaultAlgorithms()
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 voidtestFileBasedClientSessionCache()

        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 voidtestLongTimeout()
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 voidtestMultithreadedClose()
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 voidtestMultithreadedFetch()
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 voidtestRepeatedClose()
Does repeated requests for each of the hosts, with the connection being closed in between.

throws
IOException If a problem occurs.

        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 voidtestRepeatedKeepAlive()
Does repeated requests for each of the hosts, with the connection being kept alive in between.

throws
IOException If a problem occurs.

        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 voidtestSSLHandshakeHangClose()

        
        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 voidtestSSLHandshakeHangTimeout()

        
        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 voidtestShortTimeout()
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.

throws
IOException If a problem occurs.

        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 voidtestSimple()
Does a single request for each of the hosts. Consumes the response.

throws
IOException If a problem occurs.

        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 voidxxtestBrokenConnection()
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 voidxxtestBrokenConnection2()
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);