Home | History | Annotate | Download | only in core
      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 com.android.org.conscrypt.FileClientSessionCache;
     22 import com.android.org.conscrypt.OpenSSLContextImpl;
     23 import com.android.org.conscrypt.SSLClientSessionCache;
     24 import org.apache.commons.codec.binary.Base64;
     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      */
     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