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