alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Android example source code file (SSLSocketTest.java)

This example Android source code file (SSLSocketTest.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Android by Example" TM.

Java - Android tags/keywords

clientsessioncacheproxy, exception, fakeclientsessioncache, inetsocketaddress, io, ioexception, list, net, network, override, security, ssl, sslcontext, sslsockettest, string, testtrustmanager, thread, tls, util, x509certificate

The SSLSocketTest.java Android example source code

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed 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 android.core;

import junit.framework.TestCase;

import org.apache.commons.codec.binary.Base64;
import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.KeyManagementException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

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.X509TrustManager;

/**
 * SSL integration tests that hit real servers.
 */
public class SSLSocketTest extends TestCase {

    private static SSLSocketFactory clientFactory =
            (SSLSocketFactory) SSLSocketFactory.getDefault();

    /**
     * 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.
     */
    private void fetch(SSLSocketFactory socketFactory, String host, int port,
            boolean secure, String path, int outerLoop, int innerLoop,
            int delay, int timeout) throws IOException {
        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();
        }
    }

    /**
     * Invokes fetch() with the default socket factory.
     */
    private void fetch(String host, int port, boolean secure, String path,
            int outerLoop, int innerLoop,
            int delay, int timeout) throws IOException {
        fetch(clientFactory, host, port, secure, path, outerLoop, innerLoop,
                delay, timeout);
    }

    /**
     * Does a single request for each of the hosts. Consumes the response.
     *
     * @throws IOException If a problem occurs.
     */
    public void testSimple() throws IOException {
        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);
    }

    /**
     * Does repeated requests for each of the hosts, with the connection being
     * closed in between.
     *
     * @throws IOException If a problem occurs.
     */
    public void testRepeatedClose() throws IOException {
        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);
    }

    /**
     * Does repeated requests for each of the hosts, with the connection being
     * kept alive in between.
     *
     * @throws IOException If a problem occurs.
     */
    public void testRepeatedKeepAlive() throws IOException {
        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);
    }

    /**
     * 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.
     */
    public void testShortTimeout() throws IOException {
        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);
    }

    /**
     * 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.
     */
    public void testLongTimeout() {
        // 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);
    }

    /**
     * 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.
     */
    // These two need manual interaction to reproduce...
    public void xxtestBrokenConnection() {
        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);
    }

    /**
     * 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.
     */
    // These two need manual interaction to reproduce...
    public void xxtestBrokenConnection2() {
        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);
    }

    /**
     * Regression test for 865926: SSLContext.init() should
     * use default values for null arguments.
     */
    public void testContextInitNullArgs() throws Exception {
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, null, null);
    }

    /**
     * Regression test for 963650: javax.net.ssl.KeyManager has no implemented
     * (documented?) algorithms.
     */
    public void testDefaultAlgorithms() throws Exception {
            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());
    }

    /**
     * Regression test for problem where close() resulted in a hand if
     * a different thread was sitting in a blocking read or write.
     */
    public void testMultithreadedClose() throws Exception {
            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);
    }

    private int multithreadedFetchRuns;

    private int multithreadedFetchWins;

    private Random multithreadedFetchRandom = new Random();

    /**
     * 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.
     */
    public void testMultithreadedFetch() {
        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);
    }

    // -------------------------------------------------------------------------
    // Regression test for #1204316: Missing client cert unit test. Passes on
    // both Android and the RI. To use on the RI, install Apache Commons and
    // replace the references to the base64-encoded keys by the JKS versions.
    // -------------------------------------------------------------------------
    
    /** 
     * 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_JKS = 
        "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";

    /** 
     * 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 SERVER_KEYS_BKS = 
        "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";
    
    /** 
     * 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_JKS = 
        "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";

    /** 
     * 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 CLIENT_KEYS_BKS = 
        "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";    
    /** 
     * Defines the password for the keystore.
     */
    private static final String PASSWORD = "android";
            
    /** 
     * Implements basically a dummy TrustManager. It stores the certificate
     * chain it sees, so it can later be queried.
     */
    class TestTrustManager implements X509TrustManager {
        
        private X509Certificate[] chain;
        
        private String authType;
        
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
            this.chain = chain;
            this.authType = authType;
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            this.chain = chain;
            this.authType = authType;
        }

        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }

        public X509Certificate[] getChain() {
            return chain;
        }
        
        public String getAuthType() {
            return authType;
        }
        
    }
    
    /** 
     * Implements a test SSL socket server. It wait for a connection on a given
     * port, requests client authentication (if specified), and read 256 bytes
     * from the socket. 
     */
    class TestServer implements Runnable {

        public static final int CLIENT_AUTH_NONE = 0;

        public static final int CLIENT_AUTH_WANTED = 1;

        public static final int CLIENT_AUTH_NEEDED = 2;
        
        private TestTrustManager trustManager;

        private Exception exception;

        private int port;
        
        private int clientAuth;
        
        private boolean provideKeys;

        public TestServer(int port, boolean provideKeys, int clientAuth) {
            this.port = port;
            this.clientAuth = clientAuth;
            this.provideKeys = provideKeys;
            
            trustManager = new TestTrustManager(); 
        }
        
        public void run() {
            try {
                KeyManager[] keyManagers = provideKeys
                        ? getKeyManagers(SERVER_KEYS_BKS) : null;
                TrustManager[] trustManagers = new TrustManager[] {
                        trustManager };

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(keyManagers, trustManagers, null);
                
                SSLServerSocket serverSocket
                        = (SSLServerSocket) sslContext.getServerSocketFactory()
                        .createServerSocket();
                
                if (clientAuth == CLIENT_AUTH_WANTED) {
                    serverSocket.setWantClientAuth(true);
                } else if (clientAuth == CLIENT_AUTH_NEEDED) {
                    serverSocket.setNeedClientAuth(true);
                } else {
                    serverSocket.setWantClientAuth(false);
                }
                
                serverSocket.bind(new InetSocketAddress(port));
                
                SSLSocket clientSocket = (SSLSocket) serverSocket.accept();

                InputStream stream = clientSocket.getInputStream();

                for (int i = 0; i < 256; i++) {
                    int j = stream.read();
                    if (i != j) {
                        throw new RuntimeException("Error reading socket,"
                                + " expected " + i + ", got " + j);
                    }
                }
                
                stream.close();
                clientSocket.close();
                serverSocket.close();
                
            } catch (Exception ex) {
                exception = ex;
            }
        }

        public Exception getException() {
            return exception;
        }
        
        public X509Certificate[] getChain() {
            return trustManager.getChain();
        }
        
    }

    /** 
     * Implements a test SSL socket client. It open a connection to localhost on
     * a given port and writes 256 bytes to the socket. 
     */
    class TestClient implements Runnable {
        
        private TestTrustManager trustManager;

        private Exception exception;
        
        private int port;
        
        private boolean provideKeys;
        
        public TestClient(int port, boolean provideKeys) {
            this.port = port;
            this.provideKeys = provideKeys;
            
            trustManager = new TestTrustManager(); 
        }
        
        public void run() {
            try {
                KeyManager[] keyManagers = provideKeys
                        ? getKeyManagers(CLIENT_KEYS_BKS) : null;
                TrustManager[] trustManagers = new TrustManager[] {
                        trustManager };

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(keyManagers, trustManagers, null);
                
                SSLSocket socket = (SSLSocket) sslContext.getSocketFactory()
                        .createSocket();

                socket.connect(new InetSocketAddress(port));
                socket.startHandshake();

                OutputStream stream = socket.getOutputStream();
                
                for (int i = 0; i < 256; i++) {
                    stream.write(i);
                }
                
                stream.flush();
                stream.close();
                socket.close();
                
            } catch (Exception ex) {
                exception = ex;
            }
        }

        public Exception getException() {
            return exception;
        }

        public X509Certificate[] getChain() {
            return trustManager.getChain();
        }
        
    }
    
    /**
     * Loads a keystore from a base64-encoded String. Returns the KeyManager[]
     * for the result.
     */
    private KeyManager[] getKeyManagers(String keys) throws Exception {
        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();
    }

    /**
     * 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.)
     */
    public void testClientAuth() {
        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);
        }
    }

    // -------------------------------------------------------------------------
    private SSLSocket handshakeSocket;

    private Exception handshakeException;

    
    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 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.");
        }
    }

    /**
     * Tests our in-memory and persistent caching support.
     */
    public void testClientSessionCaching() throws IOException,
            KeyManagementException {
        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);
    }

    /**
     * Executes sequence of requests twice using given socket factory.
     */
    private void makeRequests(SSLSocketFactory socketFactory)
            throws IOException {
        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);
        }
    }

    /**
     * Fake in the sense that it doesn't actually persist anything.
     */
    static class FakeClientSessionCache implements SSLClientSessionCache {

        List<String> ops = new ArrayList();
        Map<String, byte[]> sessions = new HashMap();

        public byte[] getSessionData(String host, int port) {
            ops.add("get " + host);
            return sessions.get(host);
        }

        public void putSessionData(SSLSession session, byte[] sessionData) {
            String host = session.getPeerHost();
            System.err.println("length: " + sessionData.length);
            ops.add("put " + host);
            sessions.put(host, sessionData);
        }
    }

    public void testFileBasedClientSessionCache() throws IOException,
            KeyManagementException {
        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);
        }
    }

    private static void deleteDir(File directory) {
        if (!directory.exists()) {
            return;
        }
        for (File file : directory.listFiles()) {
            file.delete();
        }
        directory.delete();
    }

    static class ClientSessionCacheProxy implements SSLClientSessionCache {

        final SSLClientSessionCache delegate;
        final List<String> ops = new ArrayList();

        ClientSessionCacheProxy(SSLClientSessionCache delegate) {
            this.delegate = delegate;
        }

        public byte[] getSessionData(String host, int port) {
            byte[] sessionData = delegate.getSessionData(host, port);
            ops.add((sessionData == null ? "unsuccessful" : "successful")
                    + " get " + host);
            return sessionData;
        }

        public void putSessionData(SSLSession session, byte[] sessionData) {
            delegate.putSessionData(session, sessionData);
            ops.add("put " + session.getPeerHost());
        }
    }

    public static void main(String[] args) throws KeyManagementException, IOException {
        new SSLSocketTest().testFileBasedClientSessionCache();
    }
}

Other Android examples (source code examples)

Here is a short list of links related to this Android SSLSocketTest.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.