• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.core;
18 
19 import junit.framework.TestCase;
20 
21 import org.apache.commons.codec.binary.Base64;
22 import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
23 import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
24 import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
25 
26 import java.io.ByteArrayInputStream;
27 import java.io.DataInputStream;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.io.PrintWriter;
33 import java.net.InetSocketAddress;
34 import java.net.Socket;
35 import java.security.KeyStore;
36 import java.security.KeyManagementException;
37 import java.security.cert.X509Certificate;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Random;
44 
45 import javax.net.ssl.KeyManager;
46 import javax.net.ssl.KeyManagerFactory;
47 import javax.net.ssl.SSLContext;
48 import javax.net.ssl.SSLServerSocket;
49 import javax.net.ssl.SSLSession;
50 import javax.net.ssl.SSLSocket;
51 import javax.net.ssl.SSLSocketFactory;
52 import javax.net.ssl.TrustManager;
53 import javax.net.ssl.X509TrustManager;
54 
55 /**
56  * SSL integration tests that hit real servers.
57  */
58 public 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      */
fetch(SSLSocketFactory socketFactory, String host, int port, boolean secure, String path, int outerLoop, int innerLoop, int delay, int timeout)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      */
fetch(String host, int port, boolean secure, String path, int outerLoop, int innerLoop, int delay, int timeout)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      */
testSimple()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      */
testRepeatedClose()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      */
testRepeatedKeepAlive()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      */
testShortTimeout()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      */
testLongTimeout()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...
xxtestBrokenConnection()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...
xxtestBrokenConnection2()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      */
testContextInitNullArgs()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      */
testDefaultAlgorithms()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      */
testMultithreadedClose()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      */
testMultithreadedFetch()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 
checkClientTrusted(X509Certificate[] chain, String authType)591         public void checkClientTrusted(X509Certificate[] chain, String authType) {
592             this.chain = chain;
593             this.authType = authType;
594         }
595 
checkServerTrusted(X509Certificate[] chain, String authType)596         public void checkServerTrusted(X509Certificate[] chain, String authType) {
597             this.chain = chain;
598             this.authType = authType;
599         }
600 
getAcceptedIssuers()601         public X509Certificate[] getAcceptedIssuers() {
602             return new X509Certificate[0];
603         }
604 
getChain()605         public X509Certificate[] getChain() {
606             return chain;
607         }
608 
getAuthType()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 
TestServer(int port, boolean provideKeys, int clientAuth)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 
run()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 
getException()691         public Exception getException() {
692             return exception;
693         }
694 
getChain()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 
TestClient(int port, boolean provideKeys)715         public TestClient(int port, boolean provideKeys) {
716             this.port = port;
717             this.provideKeys = provideKeys;
718 
719             trustManager = new TestTrustManager();
720         }
721 
run()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 
getException()753         public Exception getException() {
754             return exception;
755         }
756 
getChain()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      */
getKeyManagers(String keys)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      */
testClientAuth()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 
testSSLHandshakeHangTimeout()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 
testSSLHandshakeHangClose()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      */
testClientSessionCaching()908     public void testClientSessionCaching() throws IOException,
909             KeyManagementException {
910         SSLContextImpl context = new SSLContextImpl();
911 
912         // Cache size = 2.
913         FakeClientSessionCache fakeCache = new FakeClientSessionCache();
914         context.engineInit(null, null, null, fakeCache, null);
915         SSLSocketFactory socketFactory = context.engineGetSocketFactory();
916         context.engineGetClientSessionContext().setSessionCacheSize(2);
917         makeRequests(socketFactory);
918         List<String> smallCacheOps = Arrays.asList(
919                 "get www.fortify.net",
920                 "put www.fortify.net",
921                 "get www.paypal.com",
922                 "put www.paypal.com",
923                 "get www.yellownet.ch",
924                 "put www.yellownet.ch",
925 
926                 // At this point, all in-memory cache requests should miss,
927                 // but the sessions will still be in the persistent cache.
928                 "get www.fortify.net",
929                 "get www.paypal.com",
930                 "get www.yellownet.ch"
931         );
932         assertEquals(smallCacheOps, fakeCache.ops);
933 
934         // Cache size = 3.
935         fakeCache = new FakeClientSessionCache();
936         context.engineInit(null, null, null, fakeCache, null);
937         socketFactory = context.engineGetSocketFactory();
938         context.engineGetClientSessionContext().setSessionCacheSize(3);
939         makeRequests(socketFactory);
940         List<String> bigCacheOps = Arrays.asList(
941                 "get www.fortify.net",
942                 "put www.fortify.net",
943                 "get www.paypal.com",
944                 "put www.paypal.com",
945                 "get www.yellownet.ch",
946                 "put www.yellownet.ch"
947 
948                 // At this point, all results should be in the in-memory
949                 // cache, and the persistent cache shouldn't be hit anymore.
950         );
951         assertEquals(bigCacheOps, fakeCache.ops);
952 
953         // Cache size = 4.
954         fakeCache = new FakeClientSessionCache();
955         context.engineInit(null, null, null, fakeCache, null);
956         socketFactory = context.engineGetSocketFactory();
957         context.engineGetClientSessionContext().setSessionCacheSize(4);
958         makeRequests(socketFactory);
959         assertEquals(bigCacheOps, fakeCache.ops);
960     }
961 
962     /**
963      * Executes sequence of requests twice using given socket factory.
964      */
makeRequests(SSLSocketFactory socketFactory)965     private void makeRequests(SSLSocketFactory socketFactory)
966             throws IOException {
967         for (int i = 0; i < 2; i++) {
968             fetch(socketFactory, "www.fortify.net", 443, true, "/sslcheck.html",
969                     1, 1, 0, 60);
970             fetch(socketFactory, "www.paypal.com", 443, true, "/",
971                     1, 1, 0, 60);
972             fetch(socketFactory, "www.yellownet.ch", 443, true, "/",
973                     1, 1, 0, 60);
974         }
975     }
976 
977     /**
978      * Fake in the sense that it doesn't actually persist anything.
979      */
980     static class FakeClientSessionCache implements SSLClientSessionCache {
981 
982         List<String> ops = new ArrayList<String>();
983         Map<String, byte[]> sessions = new HashMap<String, byte[]>();
984 
getSessionData(String host, int port)985         public byte[] getSessionData(String host, int port) {
986             ops.add("get " + host);
987             return sessions.get(host);
988         }
989 
putSessionData(SSLSession session, byte[] sessionData)990         public void putSessionData(SSLSession session, byte[] sessionData) {
991             String host = session.getPeerHost();
992             System.err.println("length: " + sessionData.length);
993             ops.add("put " + host);
994             sessions.put(host, sessionData);
995         }
996     }
997 
testFileBasedClientSessionCache()998     public void testFileBasedClientSessionCache() throws IOException,
999             KeyManagementException {
1000         SSLContextImpl context = new SSLContextImpl();
1001         String tmpDir = System.getProperty("java.io.tmpdir");
1002         if (tmpDir == null) {
1003             fail("Please set 'java.io.tmpdir' system property.");
1004         }
1005         File cacheDir = new File(tmpDir
1006                 + "/" + SSLSocketTest.class.getName() + "/cache");
1007         deleteDir(cacheDir);
1008         SSLClientSessionCache fileCache
1009                 = FileClientSessionCache.usingDirectory(cacheDir);
1010         try {
1011             ClientSessionCacheProxy cacheProxy
1012                     = new ClientSessionCacheProxy(fileCache);
1013             context.engineInit(null, null, null, cacheProxy, null);
1014             SSLSocketFactory socketFactory = context.engineGetSocketFactory();
1015             context.engineGetClientSessionContext().setSessionCacheSize(1);
1016             makeRequests(socketFactory);
1017             List<String> expected = Arrays.asList(
1018                     "unsuccessful get www.fortify.net",
1019                     "put www.fortify.net",
1020                     "unsuccessful get www.paypal.com",
1021                     "put www.paypal.com",
1022                     "unsuccessful get www.yellownet.ch",
1023                     "put www.yellownet.ch",
1024 
1025                     // At this point, all in-memory cache requests should miss,
1026                     // but the sessions will still be in the persistent cache.
1027                     "successful get www.fortify.net",
1028                     "successful get www.paypal.com",
1029                     "successful get www.yellownet.ch"
1030             );
1031             assertEquals(expected, cacheProxy.ops);
1032 
1033             // Try again now that file-based cache is populated.
1034             fileCache = FileClientSessionCache.usingDirectory(cacheDir);
1035             cacheProxy = new ClientSessionCacheProxy(fileCache);
1036             context.engineInit(null, null, null, cacheProxy, null);
1037             socketFactory = context.engineGetSocketFactory();
1038             context.engineGetClientSessionContext().setSessionCacheSize(1);
1039             makeRequests(socketFactory);
1040             expected = Arrays.asList(
1041                     "successful get www.fortify.net",
1042                     "successful get www.paypal.com",
1043                     "successful get www.yellownet.ch",
1044                     "successful get www.fortify.net",
1045                     "successful get www.paypal.com",
1046                     "successful get www.yellownet.ch"
1047             );
1048             assertEquals(expected, cacheProxy.ops);
1049         } finally {
1050             deleteDir(cacheDir);
1051         }
1052     }
1053 
deleteDir(File directory)1054     private static void deleteDir(File directory) {
1055         if (!directory.exists()) {
1056             return;
1057         }
1058         for (File file : directory.listFiles()) {
1059             file.delete();
1060         }
1061         directory.delete();
1062     }
1063 
1064     static class ClientSessionCacheProxy implements SSLClientSessionCache {
1065 
1066         final SSLClientSessionCache delegate;
1067         final List<String> ops = new ArrayList<String>();
1068 
ClientSessionCacheProxy(SSLClientSessionCache delegate)1069         ClientSessionCacheProxy(SSLClientSessionCache delegate) {
1070             this.delegate = delegate;
1071         }
1072 
getSessionData(String host, int port)1073         public byte[] getSessionData(String host, int port) {
1074             byte[] sessionData = delegate.getSessionData(host, port);
1075             ops.add((sessionData == null ? "unsuccessful" : "successful")
1076                     + " get " + host);
1077             return sessionData;
1078         }
1079 
putSessionData(SSLSession session, byte[] sessionData)1080         public void putSessionData(SSLSession session, byte[] sessionData) {
1081             delegate.putSessionData(session, sessionData);
1082             ops.add("put " + session.getPeerHost());
1083         }
1084     }
1085 
main(String[] args)1086     public static void main(String[] args) throws KeyManagementException, IOException {
1087         new SSLSocketTest().testFileBasedClientSessionCache();
1088     }
1089 }
1090