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