1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.core;
18
19import junit.framework.TestCase;
20
21import org.apache.commons.codec.binary.Base64;
22import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
23import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
24import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
25
26import java.io.ByteArrayInputStream;
27import java.io.DataInputStream;
28import java.io.File;
29import java.io.IOException;
30import java.io.InputStream;
31import java.io.OutputStream;
32import java.io.PrintWriter;
33import java.net.InetSocketAddress;
34import java.net.Socket;
35import java.security.KeyStore;
36import java.security.KeyManagementException;
37import java.security.cert.X509Certificate;
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.HashMap;
41import java.util.List;
42import java.util.Map;
43import java.util.Random;
44
45import javax.net.ssl.KeyManager;
46import javax.net.ssl.KeyManagerFactory;
47import javax.net.ssl.SSLContext;
48import javax.net.ssl.SSLServerSocket;
49import javax.net.ssl.SSLSession;
50import javax.net.ssl.SSLSocket;
51import javax.net.ssl.SSLSocketFactory;
52import javax.net.ssl.TrustManager;
53import javax.net.ssl.X509TrustManager;
54
55/**
56 * SSL integration tests that hit real servers.
57 */
58public class SSLSocketTest extends TestCase {
59
60    private static SSLSocketFactory clientFactory =
61            (SSLSocketFactory) SSLSocketFactory.getDefault();
62
63    /**
64     * Does a number of HTTPS requests on some host and consumes the response.
65     * We don't use the HttpsUrlConnection class, but do this on our own
66     * with the SSLSocket class. This gives us a chance to test the basic
67     * behavior of SSL.
68     *
69     * @param host      The host name the request is being sent to.
70     * @param port      The port the request is being sent to.
71     * @param path      The path being requested (e.g. "/index.html").
72     * @param outerLoop The number of times we reconnect and do the request.
73     * @param innerLoop The number of times we do the request for each
74     *                  connection (using HTTP keep-alive).
75     * @param delay     The delay after each request (in seconds).
76     * @throws IOException When a problem occurs.
77     */
78    private void fetch(SSLSocketFactory socketFactory, String host, int port,
79            boolean secure, String path, int outerLoop, int innerLoop,
80            int delay, int timeout) throws IOException {
81        InetSocketAddress address = new InetSocketAddress(host, port);
82
83        for (int i = 0; i < outerLoop; i++) {
84            // Connect to the remote host
85            Socket socket = secure ? socketFactory.createSocket()
86                    : new Socket();
87            if (timeout >= 0) {
88                socket.setKeepAlive(true);
89                socket.setSoTimeout(timeout * 1000);
90            }
91            socket.connect(address);
92
93            // Get the streams
94            OutputStream output = socket.getOutputStream();
95            PrintWriter writer = new PrintWriter(output);
96
97            try {
98                DataInputStream input = new DataInputStream(socket.getInputStream());
99                try {
100                    for (int j = 0; j < innerLoop; j++) {
101                        android.util.Log.d("SSLSocketTest",
102                                "GET https://" + host + path + " HTTP/1.1");
103
104                        // Send a request
105                        writer.println("GET https://" + host + path + " HTTP/1.1\r");
106                        writer.println("Host: " + host + "\r");
107                        writer.println("Connection: " +
108                                (j == innerLoop - 1 ? "Close" : "Keep-Alive")
109                                + "\r");
110                        writer.println("\r");
111                        writer.flush();
112
113                        int length = -1;
114                        boolean chunked = false;
115
116                        String line = input.readLine();
117
118                        if (line == null) {
119                            throw new IOException("No response from server");
120                            // android.util.Log.d("SSLSocketTest", "No response from server");
121                        }
122
123                        // Consume the headers, check content length and encoding type
124                        while (line != null && line.length() != 0) {
125//                    System.out.println(line);
126                            int dot = line.indexOf(':');
127                            if (dot != -1) {
128                                String key = line.substring(0, dot).trim();
129                                String value = line.substring(dot + 1).trim();
130
131                                if ("Content-Length".equalsIgnoreCase(key)) {
132                                    length = Integer.valueOf(value);
133                                } else if ("Transfer-Encoding".equalsIgnoreCase(key)) {
134                                    chunked = "Chunked".equalsIgnoreCase(value);
135                                }
136
137                            }
138                            line = input.readLine();
139                        }
140
141                        assertTrue("Need either content length or chunked encoding", length != -1
142                                || chunked);
143
144                        // Consume the content itself
145                        if (chunked) {
146                            length = Integer.parseInt(input.readLine(), 16);
147                            while (length != 0) {
148                                byte[] buffer = new byte[length];
149                                input.readFully(buffer);
150                                input.readLine();
151                                length = Integer.parseInt(input.readLine(), 16);
152                            }
153                            input.readLine();
154                        } else {
155                            byte[] buffer = new byte[length];
156                            input.readFully(buffer);
157                        }
158
159                        // Sleep for the given number of seconds
160                        try {
161                            Thread.sleep(delay * 1000);
162                        } catch (InterruptedException ex) {
163                            // Shut up!
164                        }
165                    }
166                } finally {
167                    input.close();
168                }
169            } finally {
170                writer.close();
171            }
172            // Close the connection
173            socket.close();
174        }
175    }
176
177    /**
178     * Invokes fetch() with the default socket factory.
179     */
180    private void fetch(String host, int port, boolean secure, String path,
181            int outerLoop, int innerLoop,
182            int delay, int timeout) throws IOException {
183        fetch(clientFactory, host, port, secure, path, outerLoop, innerLoop,
184                delay, timeout);
185    }
186
187    /**
188     * Does a single request for each of the hosts. Consumes the response.
189     *
190     * @throws IOException If a problem occurs.
191     */
192    public void testSimple() throws IOException {
193        fetch("www.fortify.net", 443, true, "/sslcheck.html", 1, 1, 0, 60);
194        fetch("mail.google.com", 443, true, "/mail/", 1, 1, 0, 60);
195        fetch("www.paypal.com", 443, true, "/", 1, 1, 0, 60);
196        fetch("www.yellownet.ch", 443, true, "/", 1, 1, 0, 60);
197    }
198
199    /**
200     * Does repeated requests for each of the hosts, with the connection being
201     * closed in between.
202     *
203     * @throws IOException If a problem occurs.
204     */
205    public void testRepeatedClose() throws IOException {
206        fetch("www.fortify.net", 443, true, "/sslcheck.html", 10, 1, 0, 60);
207        fetch("mail.google.com", 443, true, "/mail/", 10, 1, 0, 60);
208        fetch("www.paypal.com", 443, true, "/", 10, 1, 0, 60);
209        fetch("www.yellownet.ch", 443, true, "/", 10, 1, 0, 60);
210    }
211
212    /**
213     * Does repeated requests for each of the hosts, with the connection being
214     * kept alive in between.
215     *
216     * @throws IOException If a problem occurs.
217     */
218    public void testRepeatedKeepAlive() throws IOException {
219        fetch("www.fortify.net", 443, true, "/sslcheck.html", 1, 10, 0, 60);
220        fetch("mail.google.com", 443, true, "/mail/", 1, 10, 0, 60);
221
222        // These two don't accept keep-alive
223        // fetch("www.paypal.com", 443, "/", 1, 10);
224        // fetch("www.yellownet.ch", 443, "/", 1, 10);
225    }
226
227    /**
228     * Does repeated requests for each of the hosts, with the connection being
229     * closed in between. Waits a couple of seconds after each request, but
230     * stays within a reasonable timeout. Expectation is that the connection
231     * stays open.
232     *
233     * @throws IOException If a problem occurs.
234     */
235    public void testShortTimeout() throws IOException {
236        fetch("www.fortify.net", 443, true, "/sslcheck.html", 1, 10, 5, 60);
237        fetch("mail.google.com", 443, true, "/mail/", 1, 10, 5, 60);
238
239        // These two don't accept keep-alive
240        // fetch("www.paypal.com", 443, "/", 1, 10);
241        // fetch("www.yellownet.ch", 443, "/", 1, 10);
242    }
243
244    /**
245     * Does repeated requests for each of the hosts, with the connection being
246     * kept alive in between. Waits a longer time after each request.
247     * Expectation is that the host closes the connection.
248     */
249    public void testLongTimeout() {
250        // Seems to have a veeeery long timeout.
251        // fetch("www.fortify.net", 443, "/sslcheck.html", 1, 2, 60);
252
253        // Google has a 60s timeout, so 90s of waiting should trigger it.
254        try {
255            fetch("mail.google.com", 443, true, "/mail/", 1, 2, 90, 180);
256            fail("Oops - timeout expected.");
257        } catch (IOException ex) {
258            // Expected.
259        }
260
261        // These two don't accept keep-alive
262        // fetch("www.paypal.com", 443, "/", 1, 10);
263        // fetch("www.yellownet.ch", 443, "/", 1, 10);
264    }
265
266    /**
267     * Does repeated requests for each of the hosts, with the connection being
268     * closed in between. Waits a longer time after each request. Expectation is
269     * that the host closes the connection.
270     */
271    // These two need manual interaction to reproduce...
272    public void xxtestBrokenConnection() {
273        try {
274            fetch("www.fortify.net", 443, true, "/sslcheck.html", 1, 2, 60, 60);
275            fail("Oops - timeout expected.");
276        } catch (IOException ex) {
277            android.util.Log.d("SSLSocketTest", "Exception", ex);
278            // Expected.
279        }
280
281        // These two don't accept keep-alive
282        // fetch("www.paypal.com", 443, "/", 1, 10);
283        // fetch("www.yellownet.ch", 443, "/", 1, 10);
284    }
285
286    /**
287     * Does repeated requests for each of the hosts, with the connection being
288     * closed in between. Waits a longer time after each request. Expectation is
289     * that the host closes the connection.
290     */
291    // These two need manual interaction to reproduce...
292    public void xxtestBrokenConnection2() {
293        try {
294            fetch("www.heise.de", 80, false, "/index.html", 1, 2, 60, 60);
295            fail("Oops - timeout expected.");
296        } catch (IOException ex) {
297            android.util.Log.d("SSLSocketTest", "Exception", ex);
298            // Expected.
299        }
300
301        // These two don't accept keep-alive
302        // fetch("www.paypal.com", 443, "/", 1, 10);
303        // fetch("www.yellownet.ch", 443, "/", 1, 10);
304    }
305
306    /**
307     * Regression test for 865926: SSLContext.init() should
308     * use default values for null arguments.
309     */
310    public void testContextInitNullArgs() throws Exception {
311            SSLContext ctx = SSLContext.getInstance("TLS");
312            ctx.init(null, null, null);
313    }
314
315    /**
316     * Regression test for 963650: javax.net.ssl.KeyManager has no implemented
317     * (documented?) algorithms.
318     */
319    public void testDefaultAlgorithms() throws Exception {
320            SSLContext ctx = SSLContext.getInstance("TLS");
321            KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
322            KeyStore ks = KeyStore.getInstance("BKS");
323
324            assertEquals("X509", kmf.getAlgorithm());
325            assertEquals("X509", KeyManagerFactory.getDefaultAlgorithm());
326
327            assertEquals("BKS", ks.getType());
328            assertEquals("BKS", KeyStore.getDefaultType());
329    }
330
331    /**
332     * Regression test for problem where close() resulted in a hand if
333     * a different thread was sitting in a blocking read or write.
334     */
335    public void testMultithreadedClose() throws Exception {
336            InetSocketAddress address = new InetSocketAddress("www.fortify.net", 443);
337            final Socket socket = clientFactory.createSocket();
338            socket.connect(address);
339
340            Thread reader = new Thread() {
341                @Override
342                public void run() {
343                    try {
344                        byte[] buffer = new byte[512];
345                        InputStream stream = socket.getInputStream();
346                        socket.getInputStream().read(buffer);
347                    } catch (Exception ex) {
348                        android.util.Log.d("SSLSocketTest",
349                                "testMultithreadedClose() reader got " + ex.toString());
350                    }
351                }
352            };
353
354            Thread closer = new Thread() {
355                @Override
356                public void run() {
357                    try {
358                        Thread.sleep(5000);
359                        socket.close();
360                    } catch (Exception ex) {
361                        android.util.Log.d("SSLSocketTest",
362                                "testMultithreadedClose() closer got " + ex.toString());
363                    }
364                }
365            };
366
367            android.util.Log.d("SSLSocketTest", "testMultithreadedClose() starting reader...");
368            reader.start();
369            android.util.Log.d("SSLSocketTest", "testMultithreadedClose() starting closer...");
370            closer.start();
371
372            long t1 = System.currentTimeMillis();
373            android.util.Log.d("SSLSocketTest", "testMultithreadedClose() joining reader...");
374            reader.join(30000);
375            android.util.Log.d("SSLSocketTest", "testMultithreadedClose() joining closer...");
376            closer.join(30000);
377            long t2 = System.currentTimeMillis();
378
379            assertTrue("Concurrent close() hangs", t2 - t1 < 30000);
380    }
381
382    private int multithreadedFetchRuns;
383
384    private int multithreadedFetchWins;
385
386    private Random multithreadedFetchRandom = new Random();
387
388    /**
389     * Regression test for problem where multiple threads with multiple SSL
390     * connection would cause problems due to either missing native locking
391     * or the slowness of the SSL connections.
392     */
393    public void testMultithreadedFetch() {
394        Thread[] threads = new Thread[10];
395
396        for (int i = 0; i < threads.length; i++) {
397            threads[i] = new Thread() {
398                @Override
399                public void run() {
400                    for (int i = 0; i < 10; i++) {
401                        try {
402                            multithreadedFetchRuns++;
403                            switch (multithreadedFetchRandom.nextInt(4)) {
404                                case 0: {
405                                    fetch("www.fortify.net", 443,
406                                            true, "/sslcheck.html", 1, 1, 0, 60);
407                                    break;
408                                }
409
410                                case 1: {
411                                    fetch("mail.google.com", 443, true, "/mail/", 1, 1, 0, 60);
412                                    break;
413                                }
414
415                                case 2: {
416                                    fetch("www.paypal.com", 443, true, "/", 1, 1, 0, 60);
417                                    break;
418                                }
419
420                                case 3: {
421                                    fetch("www.yellownet.ch", 443, true, "/", 1, 1, 0, 60);
422                                    break;
423                                }
424                            }
425                            multithreadedFetchWins++;
426                        } catch (Exception ex) {
427                            android.util.Log.d("SSLSocketTest",
428                                    "testMultithreadedFetch() got Exception", ex);
429                        }
430                    }
431                }
432            };
433            threads[i].start();
434
435            android.util.Log.d("SSLSocketTest", "testMultithreadedFetch() started thread #" + i);
436        }
437
438        for (int i = 0; i < threads.length; i++) {
439            try {
440                threads[i].join();
441                android.util.Log.d("SSLSocketTest", "testMultithreadedFetch() joined thread #" + i);
442            } catch (InterruptedException ex) {
443                // Not interested.
444            }
445        }
446
447        assertTrue("At least 95% of multithreaded SSL connections must succeed",
448                multithreadedFetchWins >= (multithreadedFetchRuns * 95) / 100);
449    }
450
451    // -------------------------------------------------------------------------
452    // Regression test for #1204316: Missing client cert unit test. Passes on
453    // both Android and the RI. To use on the RI, install Apache Commons and
454    // replace the references to the base64-encoded keys by the JKS versions.
455    // -------------------------------------------------------------------------
456
457    /**
458     * Defines the keystore contents for the server, JKS version. Holds just a
459     * single self-generated key. The subject name is "Test Server".
460     */
461    private static final String SERVER_KEYS_JKS =
462        "/u3+7QAAAAIAAAABAAAAAQAFbXlrZXkAAAEaWFfBeAAAArowggK2MA4GCisGAQQBKgIRAQEFAASC" +
463        "AqI2kp5XjnF8YZkhcF92YsJNQkvsmH7zqMM87j23zSoV4DwyE3XeC/gZWq1ToScIhoqZkzlbWcu4" +
464        "T/Zfc/DrfGk/rKbBL1uWKGZ8fMtlZk8KoAhxZk1JSyJvdkyKxqmzUbxk1OFMlN2VJNu97FPVH+du" +
465        "dvjTvmpdoM81INWBW/1fZJeQeDvn4mMbbe0IxgpiLnI9WSevlaDP/sm1X3iO9yEyzHLL+M5Erspo" +
466        "Cwa558fOu5DdsICMXhvDQxjWFKFhPHnKtGe+VvwkG9/bAaDgx3kfhk0w5zvdnkKb+8Ed9ylNRzdk" +
467        "ocAa/mxlMTOsTvDKXjjsBupNPIIj7OP4GNnZaxkJjSs98pEO67op1GX2qhy6FSOPNuq8k/65HzUc" +
468        "PYn6voEeh6vm02U/sjEnzRevQ2+2wXoAdp0EwtQ/DlMe+NvcwPGWKuMgX4A4L93DZGb04N2VmAU3" +
469        "YLOtZwTO0LbuWrcCM/q99G/7LcczkxIVrO2I/rh8RXVczlf9QzcrFObFv4ATuspWJ8xG7DhsMbnk" +
470        "rT94Pq6TogYeoz8o8ZMykesAqN6mt/9+ToIemmXv+e+KU1hI5oLwWMnUG6dXM6hIvrULY6o+QCPH" +
471        "172YQJMa+68HAeS+itBTAF4Clm/bLn6reHCGGU6vNdwU0lYldpiOj9cB3t+u2UuLo6tiFWjLf5Zs" +
472        "EQJETd4g/EK9nHxJn0GAKrWnTw7pEHQJ08elzUuy04C/jEEG+4QXU1InzS4o/kR0Sqz2WTGDoSoq" +
473        "ewuPRU5bzQs/b9daq3mXrnPtRBL6HfSDAdpTK76iHqLCGdqx3avHjVSBm4zFvEuYBCev+3iKOBmg" +
474        "yh7eQRTjz4UOWfy85omMBr7lK8PtfVBDzOXpasxS0uBgdUyBDX4tO6k9jZ8a1kmQRQAAAAEABVgu" +
475        "NTA5AAACSDCCAkQwggGtAgRIR8SKMA0GCSqGSIb3DQEBBAUAMGkxCzAJBgNVBAYTAlVTMRMwEQYD" +
476        "VQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQHEwNNVFYxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMH" +
477        "QW5kcm9pZDEUMBIGA1UEAxMLVGVzdCBTZXJ2ZXIwHhcNMDgwNjA1MTA0ODQyWhcNMDgwOTAzMTA0" +
478        "ODQyWjBpMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8w" +
479        "DQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMIGf" +
480        "MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwoC6chqCI84rj1PrXuJgbiit4EV909zR6N0jNlYfg" +
481        "itwB39bP39wH03rFm8T59b3mbSptnGmCIpLZn25KPPFsYD3JJ+wFlmiUdEP9H05flfwtFQJnw9uT" +
482        "3rRIdYVMPcQ3RoZzwAMliGr882I2thIDbA6xjGU/1nRIdvk0LtxH3QIDAQABMA0GCSqGSIb3DQEB" +
483        "BAUAA4GBAJn+6YgUlY18Ie+0+Vt8oEi81DNi/bfPrAUAh63fhhBikx/3R9dl3wh09Z6p7cIdNxjW" +
484        "n2ll+cRW9eqF7z75F0Omm0C7/KAEPjukVbszmzeU5VqzkpSt0j84YWi+TfcHRrfvhLbrlmGITVpY" +
485        "ol5pHLDyqGmDs53pgwipWqsn/nEXEBgj3EoqPeqHbDf7YaP8h/5BSt0=";
486
487    /**
488     * Defines the keystore contents for the server, BKS version. Holds just a
489     * single self-generated key. The subject name is "Test Server".
490     */
491    private static final String SERVER_KEYS_BKS =
492        "AAAAAQAAABQDkebzoP1XwqyWKRCJEpn/t8dqIQAABDkEAAVteWtleQAAARpYl20nAAAAAQAFWC41" +
493        "MDkAAAJNMIICSTCCAbKgAwIBAgIESEfU1jANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJVUzET" +
494        "MBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNV" +
495        "BAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMB4XDTA4MDYwNTExNTgxNFoXDTA4MDkw" +
496        "MzExNTgxNFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01U" +
497        "VjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRQwEgYDVQQDEwtUZXN0IFNlcnZl" +
498        "cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0LIdKaIr9/vsTq8BZlA3R+NFWRaH4lGsTAQy" +
499        "DPMF9ZqEDOaL6DJuu0colSBBBQ85hQTPa9m9nyJoN3pEi1hgamqOvQIWcXBk+SOpUGRZZFXwniJV" +
500        "zDKU5nE9MYgn2B9AoiH3CSuMz6HRqgVaqtppIe1jhukMc/kHVJvlKRNy9XMCAwEAATANBgkqhkiG" +
501        "9w0BAQUFAAOBgQC7yBmJ9O/eWDGtSH9BH0R3dh2NdST3W9hNZ8hIa8U8klhNHbUCSSktZmZkvbPU" +
502        "hse5LI3dh6RyNDuqDrbYwcqzKbFJaq/jX9kCoeb3vgbQElMRX8D2ID1vRjxwlALFISrtaN4VpWzV" +
503        "yeoHPW4xldeZmoVtjn8zXNzQhLuBqX2MmAAAAqwAAAAUvkUScfw9yCSmALruURNmtBai7kQAAAZx" +
504        "4Jmijxs/l8EBaleaUru6EOPioWkUAEVWCxjM/TxbGHOi2VMsQWqRr/DZ3wsDmtQgw3QTrUK666sR" +
505        "MBnbqdnyCyvM1J2V1xxLXPUeRBmR2CXorYGF9Dye7NkgVdfA+9g9L/0Au6Ugn+2Cj5leoIgkgApN" +
506        "vuEcZegFlNOUPVEs3SlBgUF1BY6OBM0UBHTPwGGxFBBcetcuMRbUnu65vyDG0pslT59qpaR0TMVs" +
507        "P+tcheEzhyjbfM32/vwhnL9dBEgM8qMt0sqF6itNOQU/F4WGkK2Cm2v4CYEyKYw325fEhzTXosck" +
508        "MhbqmcyLab8EPceWF3dweoUT76+jEZx8lV2dapR+CmczQI43tV9btsd1xiBbBHAKvymm9Ep9bPzM" +
509        "J0MQi+OtURL9Lxke/70/MRueqbPeUlOaGvANTmXQD2OnW7PISwJ9lpeLfTG0LcqkoqkbtLKQLYHI" +
510        "rQfV5j0j+wmvmpMxzjN3uvNajLa4zQ8l0Eok9SFaRr2RL0gN8Q2JegfOL4pUiHPsh64WWya2NB7f" +
511        "V+1s65eA5ospXYsShRjo046QhGTmymwXXzdzuxu8IlnTEont6P4+J+GsWk6cldGbl20hctuUKzyx" +
512        "OptjEPOKejV60iDCYGmHbCWAzQ8h5MILV82IclzNViZmzAapeeCnexhpXhWTs+xDEYSKEiG/camt" +
513        "bhmZc3BcyVJrW23PktSfpBQ6D8ZxoMfF0L7V2GQMaUg+3r7ucrx82kpqotjv0xHghNIm95aBr1Qw" +
514        "1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl" +
515        "k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw=";
516
517    /**
518     * Defines the keystore contents for the client, JKS version. Holds just a
519     * single self-generated key. The subject name is "Test Client".
520     */
521    private static final String CLIENT_KEYS_JKS =
522        "/u3+7QAAAAIAAAABAAAAAQAFbXlrZXkAAAEaWFhyMAAAArkwggK1MA4GCisGAQQBKgIRAQEFAASC" +
523        "AqGVSfXolBStZy4nnRNn4fAr+S7kfU2BS23wwW8uB2Ru3GvtLzlK9q08Gvq/LNqBafjyFTVL5FV5" +
524        "SED/8YomO5a98GpskSeRvytCiTBLJdgGhws5TOGekgIAcBROPGIyOtJPQ0HfOQs+BqgzGDHzHQhw" +
525        "u/8Tm6yQwiP+W/1I9B1QnaEztZA3mhTyMMJsmsFTYroGgAog885D5Cmzd8sYGfxec3R6I+xcmBAY" +
526        "eibR5kGpWwt1R+qMvRrtBqh5r6WSKhCBNax+SJVbtUNRiKyjKccdJg6fGqIWWeivwYTy0OhjA6b4" +
527        "NiZ/ZZs5pxFGWUj/Rlp0RYy8fCF6aw5/5s4Bf4MI6dPSqMG8Hf7sJR91GbcELyzPdM0h5lNavgit" +
528        "QPEzKeuDrGxhY1frJThBsNsS0gxeu+OgfJPEb/H4lpYX5IvuIGbWKcxoO9zq4/fimIZkdA8A+3eY" +
529        "mfDaowvy65NBVQPJSxaOyFhLHfeLqOeCsVENAea02vA7andZHTZehvcrqyKtm+z8ncHGRC2H9H8O" +
530        "jKwKHfxxrYY/jMAKLl00+PBb3kspO+BHI2EcQnQuMw/zr83OR9Meq4TJ0TMuNkApZELAeFckIBbS" +
531        "rBr8NNjAIfjuCTuKHhsTFWiHfk9ZIzigxXagfeDRiyVc6khOuF/bGorj23N2o7Rf3uLoU6PyXWi4" +
532        "uhctR1aL6NzxDoK2PbYCeA9hxbDv8emaVPIzlVwpPK3Ruvv9mkjcOhZ74J8bPK2fQmbplbOljcZi" +
533        "tZijOfzcO/11JrwhuJZRA6wanTqHoujgChV9EukVrmbWGGAcewFnAsSbFXIik7/+QznXaDIt5NgL" +
534        "H/Bcz4Z/fdV7Ae1eUaxKXdPbI//4J+8liVT/d8awjW2tldIaDlmGMR3aoc830+3mAAAAAQAFWC41" +
535        "MDkAAAJIMIICRDCCAa0CBEhHxLgwDQYJKoZIhvcNAQEEBQAwaTELMAkGA1UEBhMCVVMxEzARBgNV" +
536        "BAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01UVjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdB" +
537        "bmRyb2lkMRQwEgYDVQQDEwtUZXN0IENsaWVudDAeFw0wODA2MDUxMDQ5MjhaFw0wODA5MDMxMDQ5" +
538        "MjhaMGkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQHEwNNVFYxDzAN" +
539        "BgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEUMBIGA1UEAxMLVGVzdCBDbGllbnQwgZ8w" +
540        "DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIK3Q+KiFbmCGg422TAo4gggdhMH6FJhiuz8DxRyeMKR" +
541        "UAfP4MK0wtc8N42waZ6OKvxpBFUy0BRfBsX0GD4Ku99yu9/tavSigTraeJtwV3WWRRjIqk7L3wX5" +
542        "cmgS2KSD43Y0rNUKrko26lnt9N4qiYRBSj+tcAN3Lx9+ptqk1LApAgMBAAEwDQYJKoZIhvcNAQEE" +
543        "BQADgYEANb7Q1GVSuy1RPJ0FmiXoMYCCtvlRLkmJphwxovK0cAQK12Vll+yAzBhHiQHy/RA11mng" +
544        "wYudC7u3P8X/tBT8GR1Yk7QW3KgFyPafp3lQBBCraSsfrjKj+dCLig1uBLUr4f68W8VFWZWWTHqp" +
545        "NMGpCX6qmjbkJQLVK/Yfo1ePaUexPSOX0G9m8+DoV3iyNw6at01NRw==";
546
547    /**
548     * Defines the keystore contents for the client, BKS version. Holds just a
549     * single self-generated key. The subject name is "Test Client".
550     */
551    private static final String CLIENT_KEYS_BKS =
552        "AAAAAQAAABT4Rka6fxbFps98Y5k2VilmbibNkQAABfQEAAVteWtleQAAARpYl+POAAAAAQAFWC41" +
553        "MDkAAAJNMIICSTCCAbKgAwIBAgIESEfU9TANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJVUzET" +
554        "MBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNV" +
555        "BAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgQ2xpZW50MB4XDTA4MDYwNTExNTg0NVoXDTA4MDkw" +
556        "MzExNTg0NVowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01U" +
557        "VjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRQwEgYDVQQDEwtUZXN0IENsaWVu" +
558        "dDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApUvmWsQDHPpbDKK13Yez2/q54tTOmRml/qva" +
559        "2K6dZjkjSTW0iRuk7ztaVEvdJpfVIDv1oBsCI51ttyLHROy1epjF+GoL74mJb7fkcd0VOoSOTjtD" +
560        "+3GgZkHPAm5YmUYxiJXqxKKJJqMCTIW46eJaA2nAep9QIwZ14/NFAs4ObV8CAwEAATANBgkqhkiG" +
561        "9w0BAQUFAAOBgQCJrCr3hZQFDlLIfsSKI1/w+BLvyf4fubOid0pBxfklR8KBNPTiqjSmu7pd/C/F" +
562        "1FR8CdZUDoPflZHCOU+fj5r5KUC1HyigY/tEUvlforBpfB0uCF+tXW4DbUfOWhfMtLV4nCOJOOZg" +
563        "awfZLJWBJouLKOp427vDftxTSB+Ks8YjlgAAAqwAAAAU+NH6TtrzjyDdCXm5B6Vo7xX5G4YAAAZx" +
564        "EAUkcZtmykn7YdaYxC1jRFJ+GEJpC8nZVg83QClVuCSIS8a5f8Hl44Bk4oepOZsPzhtz3RdVzDVi" +
565        "RFfoyZFsrk9F5bDTVJ6sQbb/1nfJkLhZFXokka0vND5AXMSoD5Bj1Fqem3cK7fSUyqKvFoRKC3XD" +
566        "FQvhqoam29F1rbl8FaYdPvhhZo8TfZQYUyUKwW+RbR44M5iHPx+ykieMe/C/4bcM3z8cwIbYI1aO" +
567        "gjQKS2MK9bs17xaDzeAh4sBKrskFGrDe+2dgvrSKdoakJhLTNTBSG6m+rzqMSCeQpafLKMSjTSSz" +
568        "+KoQ9bLyax8cbvViGGju0SlVhquloZmKOfHr8TukIoV64h3uCGFOVFtQjCYDOq6NbfRvMh14UVF5" +
569        "zgDIGczoD9dMoULWxBmniGSntoNgZM+QP6Id7DBasZGKfrHIAw3lHBqcvB5smemSu7F4itRoa3D8" +
570        "N7hhUEKAc+xA+8NKmXfiCBoHfPHTwDvt4IR7gWjeP3Xv5vitcKQ/MAfO5RwfzkYCXQ3FfjfzmsE1" +
571        "1IfLRDiBj+lhQSulhRVStKI88Che3M4JUNGKllrc0nt1pWa1vgzmUhhC4LSdm6trTHgyJnB6OcS9" +
572        "t2furYjK88j1AuB4921oxMxRm8c4Crq8Pyuf+n3YKi8Pl2BzBtw++0gj0ODlgwut8SrVj66/nvIB" +
573        "jN3kLVahR8nZrEFF6vTTmyXi761pzq9yOVqI57wJGx8o3Ygox1p+pWUPl1hQR7rrhUbgK/Q5wno9" +
574        "uJk07h3IZnNxE+/IKgeMTP/H4+jmyT4mhsexJ2BFHeiKF1KT/FMcJdSi+ZK5yoNVcYuY8aZbx0Ef" +
575        "lHorCXAmLFB0W6Cz4KPP01nD9YBB4olxiK1t7m0AU9zscdivNiuUaB5OIEr+JuZ6dNw=";
576    /**
577     * Defines the password for the keystore.
578     */
579    private static final String PASSWORD = "android";
580
581    /**
582     * Implements basically a dummy TrustManager. It stores the certificate
583     * chain it sees, so it can later be queried.
584     */
585    class TestTrustManager implements X509TrustManager {
586
587        private X509Certificate[] chain;
588
589        private String authType;
590
591        public void checkClientTrusted(X509Certificate[] chain, String authType) {
592            this.chain = chain;
593            this.authType = authType;
594        }
595
596        public void checkServerTrusted(X509Certificate[] chain, String authType) {
597            this.chain = chain;
598            this.authType = authType;
599        }
600
601        public X509Certificate[] getAcceptedIssuers() {
602            return new X509Certificate[0];
603        }
604
605        public X509Certificate[] getChain() {
606            return chain;
607        }
608
609        public String getAuthType() {
610            return authType;
611        }
612
613    }
614
615    /**
616     * Implements a test SSL socket server. It wait for a connection on a given
617     * port, requests client authentication (if specified), and read 256 bytes
618     * from the socket.
619     */
620    class TestServer implements Runnable {
621
622        public static final int CLIENT_AUTH_NONE = 0;
623
624        public static final int CLIENT_AUTH_WANTED = 1;
625
626        public static final int CLIENT_AUTH_NEEDED = 2;
627
628        private TestTrustManager trustManager;
629
630        private Exception exception;
631
632        private int port;
633
634        private int clientAuth;
635
636        private boolean provideKeys;
637
638        public TestServer(int port, boolean provideKeys, int clientAuth) {
639            this.port = port;
640            this.clientAuth = clientAuth;
641            this.provideKeys = provideKeys;
642
643            trustManager = new TestTrustManager();
644        }
645
646        public void run() {
647            try {
648                KeyManager[] keyManagers = provideKeys
649                        ? getKeyManagers(SERVER_KEYS_BKS) : null;
650                TrustManager[] trustManagers = new TrustManager[] {
651                        trustManager };
652
653                SSLContext sslContext = SSLContext.getInstance("TLS");
654                sslContext.init(keyManagers, trustManagers, null);
655
656                SSLServerSocket serverSocket
657                        = (SSLServerSocket) sslContext.getServerSocketFactory()
658                        .createServerSocket();
659
660                if (clientAuth == CLIENT_AUTH_WANTED) {
661                    serverSocket.setWantClientAuth(true);
662                } else if (clientAuth == CLIENT_AUTH_NEEDED) {
663                    serverSocket.setNeedClientAuth(true);
664                } else {
665                    serverSocket.setWantClientAuth(false);
666                }
667
668                serverSocket.bind(new InetSocketAddress(port));
669
670                SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
671
672                InputStream stream = clientSocket.getInputStream();
673
674                for (int i = 0; i < 256; i++) {
675                    int j = stream.read();
676                    if (i != j) {
677                        throw new RuntimeException("Error reading socket,"
678                                + " expected " + i + ", got " + j);
679                    }
680                }
681
682                stream.close();
683                clientSocket.close();
684                serverSocket.close();
685
686            } catch (Exception ex) {
687                exception = ex;
688            }
689        }
690
691        public Exception getException() {
692            return exception;
693        }
694
695        public X509Certificate[] getChain() {
696            return trustManager.getChain();
697        }
698
699    }
700
701    /**
702     * Implements a test SSL socket client. It open a connection to localhost on
703     * a given port and writes 256 bytes to the socket.
704     */
705    class TestClient implements Runnable {
706
707        private TestTrustManager trustManager;
708
709        private Exception exception;
710
711        private int port;
712
713        private boolean provideKeys;
714
715        public TestClient(int port, boolean provideKeys) {
716            this.port = port;
717            this.provideKeys = provideKeys;
718
719            trustManager = new TestTrustManager();
720        }
721
722        public void run() {
723            try {
724                KeyManager[] keyManagers = provideKeys
725                        ? getKeyManagers(CLIENT_KEYS_BKS) : null;
726                TrustManager[] trustManagers = new TrustManager[] {
727                        trustManager };
728
729                SSLContext sslContext = SSLContext.getInstance("TLS");
730                sslContext.init(keyManagers, trustManagers, null);
731
732                SSLSocket socket = (SSLSocket) sslContext.getSocketFactory()
733                        .createSocket();
734
735                socket.connect(new InetSocketAddress(port));
736                socket.startHandshake();
737
738                OutputStream stream = socket.getOutputStream();
739
740                for (int i = 0; i < 256; i++) {
741                    stream.write(i);
742                }
743
744                stream.flush();
745                stream.close();
746                socket.close();
747
748            } catch (Exception ex) {
749                exception = ex;
750            }
751        }
752
753        public Exception getException() {
754            return exception;
755        }
756
757        public X509Certificate[] getChain() {
758            return trustManager.getChain();
759        }
760
761    }
762
763    /**
764     * Loads a keystore from a base64-encoded String. Returns the KeyManager[]
765     * for the result.
766     */
767    private KeyManager[] getKeyManagers(String keys) throws Exception {
768        byte[] bytes = new Base64().decode(keys.getBytes());
769        InputStream inputStream = new ByteArrayInputStream(bytes);
770
771        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
772        keyStore.load(inputStream, PASSWORD.toCharArray());
773        inputStream.close();
774
775        String algorithm = KeyManagerFactory.getDefaultAlgorithm();
776        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
777        keyManagerFactory.init(keyStore, PASSWORD.toCharArray());
778
779        return keyManagerFactory.getKeyManagers();
780    }
781
782    /**
783     * Implements the actual test case. Launches a server and a client, requires
784     * client authentication and checks the certificates afterwards (not in the
785     * usual sense, we just make sure that we got the expected certificates,
786     * because our self-signed test certificates are not valid.)
787     */
788    public void testClientAuth() {
789        try {
790            TestServer server = new TestServer(8088, true, TestServer.CLIENT_AUTH_WANTED);
791            TestClient client = new TestClient(8088, true);
792
793            Thread serverThread = new Thread(server);
794            Thread clientThread = new Thread(client);
795
796            serverThread.start();
797            clientThread.start();
798
799            serverThread.join();
800            clientThread.join();
801
802            // The server must have completed without an exception.
803            if (server.getException() != null) {
804                throw new RuntimeException(server.getException());
805            }
806
807            // The client must have completed without an exception.
808            if (client.getException() != null) {
809                throw new RuntimeException(client.getException());
810            }
811
812            // Caution: The clientChain is the certificate chain from our
813            // client object. It contains the server certificates, of course!
814            X509Certificate[] clientChain = client.getChain();
815            assertTrue("Client cert chain must not be null", clientChain != null);
816            assertTrue("Client cert chain must not be empty", clientChain.length != 0);
817            assertEquals("CN=Test Server, OU=Android, O=Google, L=MTV, ST=California, C=US", clientChain[0].getSubjectDN().toString());
818            // Important part ------^
819
820            // Caution: The serverChain is the certificate chain from our
821            // server object. It contains the client certificates, of course!
822            X509Certificate[] serverChain = server.getChain();
823            assertTrue("Server cert chain must not be null", serverChain != null);
824            assertTrue("Server cert chain must not be empty", serverChain.length != 0);
825            assertEquals("CN=Test Client, OU=Android, O=Google, L=MTV, ST=California, C=US", serverChain[0].getSubjectDN().toString());
826            // Important part ------^
827
828        } catch (Exception ex) {
829            throw new RuntimeException(ex);
830        }
831    }
832
833    // -------------------------------------------------------------------------
834    private SSLSocket handshakeSocket;
835
836    private Exception handshakeException;
837
838
839    public void testSSLHandshakeHangTimeout() {
840
841        Thread thread = new Thread() {
842            @Override
843            public void run() {
844                try {
845                    SSLSocket socket = (SSLSocket)clientFactory.createSocket(
846                            "www.heise.de", 80);
847                    socket.setSoTimeout(5000);
848                    socket.startHandshake();
849                    socket.close();
850                } catch (Exception ex) {
851                    handshakeException = ex;
852                }
853            }
854        };
855
856        thread.start();
857
858        try {
859            thread.join(10000);
860        } catch (InterruptedException ex) {
861            // Ignore.
862        }
863
864        if (handshakeException == null) {
865            fail("SSL handshake should have failed.");
866        }
867    }
868
869    public void testSSLHandshakeHangClose() {
870
871        Thread thread = new Thread() {
872            @Override
873            public void run() {
874                try {
875                    handshakeSocket = (SSLSocket)clientFactory.createSocket(
876                            "www.heise.de", 80);
877                    handshakeSocket.startHandshake();
878                } catch (Exception ex) {
879                    handshakeException = ex;
880                }
881            }
882        };
883
884        thread.start();
885
886
887        try {
888            Thread.sleep(5000);
889            try {
890                handshakeSocket.close();
891            } catch (Exception ex) {
892                throw new RuntimeException(ex);
893            }
894
895            thread.join(5000);
896        } catch (InterruptedException ex) {
897            // Ignore.
898        }
899
900        if (handshakeException == null) {
901            fail("SSL handshake should have failed.");
902        }
903    }
904
905    /**
906     * Tests our in-memory and persistent caching support.
907     */
908    public void testClientSessionCaching() throws IOException,
909            KeyManagementException {
910        OpenSSLContextImpl context = new OpenSSLContextImpl();
911
912        // Cache size = 2.
913        FakeClientSessionCache fakeCache = new FakeClientSessionCache();
914        context.engineInit(null, null, null);
915        context.engineGetClientSessionContext().setPersistentCache(fakeCache);
916        SSLSocketFactory socketFactory = context.engineGetSocketFactory();
917        context.engineGetClientSessionContext().setSessionCacheSize(2);
918        makeRequests(socketFactory);
919        List<String> smallCacheOps = Arrays.asList(
920                "get www.fortify.net",
921                "put www.fortify.net",
922                "get www.paypal.com",
923                "put www.paypal.com",
924                "get www.yellownet.ch",
925                "put www.yellownet.ch",
926
927                // At this point, all in-memory cache requests should miss,
928                // but the sessions will still be in the persistent cache.
929                "get www.fortify.net",
930                "get www.paypal.com",
931                "get www.yellownet.ch"
932        );
933        assertEquals(smallCacheOps, fakeCache.ops);
934
935        // Cache size = 3.
936        fakeCache = new FakeClientSessionCache();
937        context.engineInit(null, null, null);
938        context.engineGetClientSessionContext().setPersistentCache(fakeCache);
939        socketFactory = context.engineGetSocketFactory();
940        context.engineGetClientSessionContext().setSessionCacheSize(3);
941        makeRequests(socketFactory);
942        List<String> bigCacheOps = Arrays.asList(
943                "get www.fortify.net",
944                "put www.fortify.net",
945                "get www.paypal.com",
946                "put www.paypal.com",
947                "get www.yellownet.ch",
948                "put www.yellownet.ch"
949
950                // At this point, all results should be in the in-memory
951                // cache, and the persistent cache shouldn't be hit anymore.
952        );
953        assertEquals(bigCacheOps, fakeCache.ops);
954
955        // Cache size = 4.
956        fakeCache = new FakeClientSessionCache();
957        context.engineInit(null, null, null);
958        context.engineGetClientSessionContext().setPersistentCache(fakeCache);
959        socketFactory = context.engineGetSocketFactory();
960        context.engineGetClientSessionContext().setSessionCacheSize(4);
961        makeRequests(socketFactory);
962        assertEquals(bigCacheOps, fakeCache.ops);
963    }
964
965    /**
966     * Executes sequence of requests twice using given socket factory.
967     */
968    private void makeRequests(SSLSocketFactory socketFactory)
969            throws IOException {
970        for (int i = 0; i < 2; i++) {
971            fetch(socketFactory, "www.fortify.net", 443, true, "/sslcheck.html",
972                    1, 1, 0, 60);
973            fetch(socketFactory, "www.paypal.com", 443, true, "/",
974                    1, 1, 0, 60);
975            fetch(socketFactory, "www.yellownet.ch", 443, true, "/",
976                    1, 1, 0, 60);
977        }
978    }
979
980    /**
981     * Fake in the sense that it doesn't actually persist anything.
982     */
983    static class FakeClientSessionCache implements SSLClientSessionCache {
984
985        List<String> ops = new ArrayList<String>();
986        Map<String, byte[]> sessions = new HashMap<String, byte[]>();
987
988        public byte[] getSessionData(String host, int port) {
989            ops.add("get " + host);
990            return sessions.get(host);
991        }
992
993        public void putSessionData(SSLSession session, byte[] sessionData) {
994            String host = session.getPeerHost();
995            System.err.println("length: " + sessionData.length);
996            ops.add("put " + host);
997            sessions.put(host, sessionData);
998        }
999    }
1000
1001    public void testFileBasedClientSessionCache() throws IOException,
1002            KeyManagementException {
1003        OpenSSLContextImpl context = new OpenSSLContextImpl();
1004        String tmpDir = System.getProperty("java.io.tmpdir");
1005        if (tmpDir == null) {
1006            fail("Please set 'java.io.tmpdir' system property.");
1007        }
1008        File cacheDir = new File(tmpDir
1009                + "/" + SSLSocketTest.class.getName() + "/cache");
1010        deleteDir(cacheDir);
1011        SSLClientSessionCache fileCache
1012                = FileClientSessionCache.usingDirectory(cacheDir);
1013        try {
1014            ClientSessionCacheProxy cacheProxy
1015                    = new ClientSessionCacheProxy(fileCache);
1016            context.engineInit(null, null, null);
1017            context.engineGetClientSessionContext().setPersistentCache(cacheProxy);
1018            SSLSocketFactory socketFactory = context.engineGetSocketFactory();
1019            context.engineGetClientSessionContext().setSessionCacheSize(1);
1020            makeRequests(socketFactory);
1021            List<String> expected = Arrays.asList(
1022                    "unsuccessful get www.fortify.net",
1023                    "put www.fortify.net",
1024                    "unsuccessful get www.paypal.com",
1025                    "put www.paypal.com",
1026                    "unsuccessful get www.yellownet.ch",
1027                    "put www.yellownet.ch",
1028
1029                    // At this point, all in-memory cache requests should miss,
1030                    // but the sessions will still be in the persistent cache.
1031                    "successful get www.fortify.net",
1032                    "successful get www.paypal.com",
1033                    "successful get www.yellownet.ch"
1034            );
1035            assertEquals(expected, cacheProxy.ops);
1036
1037            // Try again now that file-based cache is populated.
1038            fileCache = FileClientSessionCache.usingDirectory(cacheDir);
1039            cacheProxy = new ClientSessionCacheProxy(fileCache);
1040            context.engineInit(null, null, null);
1041            context.engineGetClientSessionContext().setPersistentCache(cacheProxy);
1042            socketFactory = context.engineGetSocketFactory();
1043            context.engineGetClientSessionContext().setSessionCacheSize(1);
1044            makeRequests(socketFactory);
1045            expected = Arrays.asList(
1046                    "successful get www.fortify.net",
1047                    "successful get www.paypal.com",
1048                    "successful get www.yellownet.ch",
1049                    "successful get www.fortify.net",
1050                    "successful get www.paypal.com",
1051                    "successful get www.yellownet.ch"
1052            );
1053            assertEquals(expected, cacheProxy.ops);
1054        } finally {
1055            deleteDir(cacheDir);
1056        }
1057    }
1058
1059    private static void deleteDir(File directory) {
1060        if (!directory.exists()) {
1061            return;
1062        }
1063        for (File file : directory.listFiles()) {
1064            file.delete();
1065        }
1066        directory.delete();
1067    }
1068
1069    static class ClientSessionCacheProxy implements SSLClientSessionCache {
1070
1071        final SSLClientSessionCache delegate;
1072        final List<String> ops = new ArrayList<String>();
1073
1074        ClientSessionCacheProxy(SSLClientSessionCache delegate) {
1075            this.delegate = delegate;
1076        }
1077
1078        public byte[] getSessionData(String host, int port) {
1079            byte[] sessionData = delegate.getSessionData(host, port);
1080            ops.add((sessionData == null ? "unsuccessful" : "successful")
1081                    + " get " + host);
1082            return sessionData;
1083        }
1084
1085        public void putSessionData(SSLSession session, byte[] sessionData) {
1086            delegate.putSessionData(session, sessionData);
1087            ops.add("put " + session.getPeerHost());
1088        }
1089    }
1090
1091    public static void main(String[] args) throws KeyManagementException, IOException {
1092        new SSLSocketTest().testFileBasedClientSessionCache();
1093    }
1094}
1095