1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package org.apache.harmony.luni.tests.internal.net.www.protocol.https; 19 20import java.io.BufferedInputStream; 21import java.io.File; 22import java.io.FileInputStream; 23import java.io.FileNotFoundException; 24import java.io.FileOutputStream; 25import java.io.IOException; 26import java.io.InputStream; 27import java.io.OutputStream; 28import java.io.PrintStream; 29import java.net.Authenticator; 30import java.net.InetSocketAddress; 31import java.net.PasswordAuthentication; 32import java.net.Proxy; 33import java.net.ServerSocket; 34import java.net.Socket; 35import java.net.URL; 36import java.security.KeyStore; 37import java.security.cert.Certificate; 38import java.util.Arrays; 39import java.util.concurrent.Callable; 40import java.util.concurrent.ExecutionException; 41import java.util.concurrent.ExecutorService; 42import java.util.concurrent.Executors; 43import java.util.concurrent.Future; 44import java.util.concurrent.TimeUnit; 45import javax.net.ssl.HostnameVerifier; 46import javax.net.ssl.HttpsURLConnection; 47import javax.net.ssl.KeyManager; 48import javax.net.ssl.KeyManagerFactory; 49import javax.net.ssl.SSLContext; 50import javax.net.ssl.SSLServerSocket; 51import javax.net.ssl.SSLSession; 52import javax.net.ssl.SSLSocket; 53import javax.net.ssl.SSLSocketFactory; 54import javax.net.ssl.TrustManager; 55import javax.net.ssl.TrustManagerFactory; 56import junit.framework.TestCase; 57import libcore.java.security.TestKeyStore; 58import libcore.javax.net.ssl.TestTrustManager; 59 60/** 61 * Implementation independent test for HttpsURLConnection. 62 * The test needs certstore file placed in system classpath 63 * and named as "key_store." + the type of the 64 * default KeyStore installed in the system in lower case. 65 * <br> 66 * For example: if default KeyStore type in the system is BKS 67 * (i.e. java.security file sets up the property keystore.type=BKS), 68 * thus classpath should point to the directory with "key_store.bks" 69 * file. 70 * <br> 71 * This certstore file should contain self-signed certificate 72 * generated by keytool utility in a usual way. 73 * <br> 74 * The password to the certstore should be "password" (without quotes). 75 */ 76public class HttpsURLConnectionTest extends TestCase { 77 78 // the password to the store 79 private static final String KS_PASSWORD = "password"; 80 81 // turn on/off logging 82 private static final boolean DO_LOG = false; 83 84 // read/connection timeout value 85 private static final int TIMEOUT = 5000; 86 87 // OK response code 88 private static final int OK_CODE = 200; 89 90 // Not Found response code 91 private static final int NOT_FOUND_CODE = 404; 92 93 // Proxy authentication required response code 94 private static final int AUTHENTICATION_REQUIRED_CODE = 407; 95 96 private static File store; 97 98 static { 99 try { 100 store = File.createTempFile("key_store", "bks"); 101 } catch (Exception e) { 102 // ignore 103 } 104 } 105 106 /** 107 * Checks that HttpsURLConnection's default SSLSocketFactory is operable. 108 */ 109 public void testGetDefaultSSLSocketFactory() throws Exception { 110 // set up the properties defining the default values needed by SSL stuff 111 setUpStoreProperties(); 112 113 SSLSocketFactory defaultSSLSF = HttpsURLConnection.getDefaultSSLSocketFactory(); 114 ServerSocket ss = new ServerSocket(0); 115 Socket s = defaultSSLSF.createSocket("localhost", ss.getLocalPort()); 116 ss.accept(); 117 s.close(); 118 ss.close(); 119 } 120 121 public void testHttpsConnection() throws Throwable { 122 // set up the properties defining the default values needed by SSL stuff 123 setUpStoreProperties(); 124 125 // create the SSL server socket acting as a server 126 SSLContext ctx = getContext(); 127 ServerSocket ss = ctx.getServerSocketFactory().createServerSocket(0); 128 129 // create the HostnameVerifier to check hostname verification 130 TestHostnameVerifier hnv = new TestHostnameVerifier(); 131 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 132 133 // create url connection to be tested 134 URL url = new URL("https://localhost:" + ss.getLocalPort()); 135 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 136 connection.setSSLSocketFactory(ctx.getSocketFactory()); 137 138 // perform the interaction between the peers 139 SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); 140 141 // check the connection state 142 checkConnectionStateParameters(connection, peerSocket); 143 144 // should silently exit 145 connection.connect(); 146 } 147 148 /** 149 * Tests the behaviour of HTTPS connection in case of unavailability 150 * of requested resource. 151 */ 152 public void testHttpsConnection_Not_Found_Response() throws Throwable { 153 // set up the properties defining the default values needed by SSL stuff 154 setUpStoreProperties(); 155 156 // create the SSL server socket acting as a server 157 SSLContext ctx = getContext(); 158 ServerSocket ss = ctx.getServerSocketFactory().createServerSocket(0); 159 160 // create the HostnameVerifier to check hostname verification 161 TestHostnameVerifier hnv = new TestHostnameVerifier(); 162 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 163 164 // create url connection to be tested 165 URL url = new URL("https://localhost:" + ss.getLocalPort()); 166 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 167 connection.setSSLSocketFactory(ctx.getSocketFactory()); 168 169 try { 170 doInteraction(connection, ss, NOT_FOUND_CODE); 171 fail("Expected exception was not thrown."); 172 } catch (FileNotFoundException e) { 173 if (DO_LOG) { 174 System.out.println("Expected exception was thrown: " + e.getMessage()); 175 e.printStackTrace(); 176 } 177 } 178 179 // should silently exit 180 connection.connect(); 181 } 182 183 /** 184 * Tests possibility to set up the default SSLSocketFactory 185 * to be used by HttpsURLConnection. 186 */ 187 public void testSetDefaultSSLSocketFactory() throws Throwable { 188 // create the SSLServerSocket which will be used by server side 189 SSLContext ctx = getContext(); 190 SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(0); 191 192 SSLSocketFactory socketFactory = (SSLSocketFactory) ctx.getSocketFactory(); 193 // set up the factory as default 194 HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); 195 // check the result 196 assertSame("Default SSLSocketFactory differs from expected", 197 socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory()); 198 199 // create the HostnameVerifier to check hostname verification 200 TestHostnameVerifier hnv = new TestHostnameVerifier(); 201 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 202 203 // create HttpsURLConnection to be tested 204 URL url = new URL("https://localhost:" + ss.getLocalPort()); 205 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 206 207 TestHostnameVerifier hnv_late = new TestHostnameVerifier(); 208 // late initialization: should not be used for created connection 209 HttpsURLConnection.setDefaultHostnameVerifier(hnv_late); 210 211 // perform the interaction between the peers 212 SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); 213 // check the connection state 214 checkConnectionStateParameters(connection, peerSocket); 215 // check the verification process 216 assertTrue("Hostname verification was not done", hnv.verified); 217 assertFalse("Hostname verification should not be done by this verifier", 218 hnv_late.verified); 219 // check the used SSLSocketFactory 220 assertSame("Default SSLSocketFactory should be used", 221 HttpsURLConnection.getDefaultSSLSocketFactory(), 222 connection.getSSLSocketFactory()); 223 224 // should silently exit 225 connection.connect(); 226 } 227 228 /** 229 * Tests possibility to set up the SSLSocketFactory 230 * to be used by HttpsURLConnection. 231 */ 232 public void testSetSSLSocketFactory() throws Throwable { 233 // create the SSLServerSocket which will be used by server side 234 SSLContext ctx = getContext(); 235 SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(0); 236 237 // create the HostnameVerifier to check hostname verification 238 TestHostnameVerifier hnv = new TestHostnameVerifier(); 239 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 240 241 // create HttpsURLConnection to be tested 242 URL url = new URL("https://localhost:" + ss.getLocalPort()); 243 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 244 245 SSLSocketFactory socketFactory = (SSLSocketFactory) ctx.getSocketFactory(); 246 connection.setSSLSocketFactory(socketFactory); 247 248 TestHostnameVerifier hnv_late = new TestHostnameVerifier(); 249 // late initialization: should not be used for created connection 250 HttpsURLConnection.setDefaultHostnameVerifier(hnv_late); 251 252 // perform the interaction between the peers 253 SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); 254 // check the connection state 255 checkConnectionStateParameters(connection, peerSocket); 256 // check the verification process 257 assertTrue("Hostname verification was not done", hnv.verified); 258 assertFalse("Hostname verification should not be done by this verifier", 259 hnv_late.verified); 260 // check the used SSLSocketFactory 261 assertNotSame("Default SSLSocketFactory should not be used", 262 HttpsURLConnection.getDefaultSSLSocketFactory(), 263 connection.getSSLSocketFactory()); 264 assertSame("Result differs from expected", 265 socketFactory, connection.getSSLSocketFactory()); 266 267 // should silently exit 268 connection.connect(); 269 } 270 271 /** 272 * Tests the behaviour of HttpsURLConnection in case of retrieving 273 * of the connection state parameters before connection has been made. 274 */ 275 public void testUnconnectedStateParameters() throws Throwable { 276 // create HttpsURLConnection to be tested 277 URL url = new URL("https://localhost:55555"); 278 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 279 280 try { 281 connection.getCipherSuite(); 282 fail("Expected IllegalStateException was not thrown"); 283 } catch (IllegalStateException e) {} 284 try { 285 connection.getPeerPrincipal(); 286 fail("Expected IllegalStateException was not thrown"); 287 } catch (IllegalStateException e) {} 288 try { 289 connection.getLocalPrincipal(); 290 fail("Expected IllegalStateException was not thrown"); 291 } catch (IllegalStateException e) {} 292 293 try { 294 connection.getServerCertificates(); 295 fail("Expected IllegalStateException was not thrown"); 296 } catch (IllegalStateException e) {} 297 try { 298 connection.getLocalCertificates(); 299 fail("Expected IllegalStateException was not thrown"); 300 } catch (IllegalStateException e) {} 301 } 302 303 /** 304 * Tests if setHostnameVerifier() method replaces default verifier. 305 */ 306 public void testSetHostnameVerifier() throws Throwable { 307 // setting up the properties pointing to the key/trust stores 308 setUpStoreProperties(); 309 310 // create the SSLServerSocket which will be used by server side 311 SSLServerSocket ss = (SSLServerSocket) 312 getContext().getServerSocketFactory().createServerSocket(0); 313 314 // create the HostnameVerifier to check that Hostname verification 315 // is done 316 TestHostnameVerifier hnv = new TestHostnameVerifier(); 317 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 318 319 // create HttpsURLConnection to be tested 320 URL url = new URL("https://localhost:" + ss.getLocalPort()); 321 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 322 connection.setSSLSocketFactory(getContext().getSocketFactory()); 323 324 TestHostnameVerifier hnv_late = new TestHostnameVerifier(); 325 // replace default verifier 326 connection.setHostnameVerifier(hnv_late); 327 328 // perform the interaction between the peers and check the results 329 SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); 330 assertTrue("Hostname verification was not done", hnv_late.verified); 331 assertFalse("Hostname verification should not be done by this verifier", 332 hnv.verified); 333 checkConnectionStateParameters(connection, peerSocket); 334 335 // should silently exit 336 connection.connect(); 337 } 338 339 /** 340 * Tests the behaviour in case of sending the data to the server. 341 */ 342 public void test_doOutput() throws Throwable { 343 // setting up the properties pointing to the key/trust stores 344 setUpStoreProperties(); 345 346 // create the SSLServerSocket which will be used by server side 347 SSLServerSocket ss = (SSLServerSocket) 348 getContext().getServerSocketFactory().createServerSocket(0); 349 350 // create the HostnameVerifier to check that Hostname verification 351 // is done 352 TestHostnameVerifier hnv = new TestHostnameVerifier(); 353 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 354 355 // create HttpsURLConnection to be tested 356 URL url = new URL("https://localhost:" + ss.getLocalPort()); 357 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 358 connection.setSSLSocketFactory(getContext().getSocketFactory()); 359 connection.setDoOutput(true); 360 361 // perform the interaction between the peers and check the results 362 SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); 363 checkConnectionStateParameters(connection, peerSocket); 364 365 // should silently exit 366 connection.connect(); 367 } 368 369 /** 370 * Tests HTTPS connection process made through the proxy server. 371 */ 372 public void testProxyConnection() throws Throwable { 373 // setting up the properties pointing to the key/trust stores 374 setUpStoreProperties(); 375 376 // create the SSLServerSocket which will be used by server side 377 ServerSocket ss = new ServerSocket(0); 378 379 // create the HostnameVerifier to check that Hostname verification 380 // is done 381 TestHostnameVerifier hnv = new TestHostnameVerifier(); 382 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 383 384 // create HttpsURLConnection to be tested 385 URL url = new URL("https://requested.host:55556/requested.data"); 386 HttpsURLConnection connection = (HttpsURLConnection) 387 url.openConnection(new Proxy(Proxy.Type.HTTP, 388 new InetSocketAddress("localhost", 389 ss.getLocalPort()))); 390 connection.setSSLSocketFactory(getContext().getSocketFactory()); 391 392 // perform the interaction between the peers and check the results 393 SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); 394 checkConnectionStateParameters(connection, peerSocket); 395 396 // should silently exit 397 connection.connect(); 398 } 399 400 /** 401 * Tests HTTPS connection process made through the proxy server. 402 * Proxy server needs authentication. 403 */ 404 public void testProxyAuthConnection() throws Throwable { 405 // setting up the properties pointing to the key/trust stores 406 setUpStoreProperties(); 407 408 // create the SSLServerSocket which will be used by server side 409 ServerSocket ss = new ServerSocket(0); 410 411 // create the HostnameVerifier to check that Hostname verification 412 // is done 413 TestHostnameVerifier hnv = new TestHostnameVerifier(); 414 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 415 416 Authenticator.setDefault(new Authenticator() { 417 418 protected PasswordAuthentication getPasswordAuthentication() { 419 return new PasswordAuthentication("user", "password" 420 .toCharArray()); 421 } 422 }); 423 424 // create HttpsURLConnection to be tested 425 URL url = new URL("https://requested.host:55555/requested.data"); 426 HttpsURLConnection connection = (HttpsURLConnection) 427 url.openConnection(new Proxy(Proxy.Type.HTTP, 428 new InetSocketAddress("localhost", 429 ss.getLocalPort()))); 430 connection.setSSLSocketFactory(getContext().getSocketFactory()); 431 432 // perform the interaction between the peers and check the results 433 SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); 434 checkConnectionStateParameters(connection, peerSocket); 435 436 // should silently exit 437 connection.connect(); 438 } 439 440 /** 441 * Tests HTTPS connection process made through the proxy server. 442 * 2 HTTPS connections are opened for one URL. For the first time 443 * the connection is opened through one proxy, 444 * for the second time through another. 445 */ 446 public void testConsequentProxyConnection() throws Throwable { 447 // setting up the properties pointing to the key/trust stores 448 setUpStoreProperties(); 449 450 // create the SSLServerSocket which will be used by server side 451 ServerSocket ss = new ServerSocket(0); 452 453 // create the HostnameVerifier to check that Hostname verification 454 // is done 455 TestHostnameVerifier hnv = new TestHostnameVerifier(); 456 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 457 458 // create HttpsURLConnection to be tested 459 URL url = new URL("https://requested.host:55555/requested.data"); 460 HttpsURLConnection connection = (HttpsURLConnection) 461 url.openConnection(new Proxy(Proxy.Type.HTTP, 462 new InetSocketAddress("localhost", 463 ss.getLocalPort()))); 464 connection.setSSLSocketFactory(getContext().getSocketFactory()); 465 466 // perform the interaction between the peers and check the results 467 SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss); 468 checkConnectionStateParameters(connection, peerSocket); 469 470 // create another SSLServerSocket which will be used by server side 471 ss = new ServerSocket(0); 472 473 connection = (HttpsURLConnection) url.openConnection(new Proxy( 474 Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort()))); 475 connection.setSSLSocketFactory(getContext().getSocketFactory()); 476 477 // perform the interaction between the peers and check the results 478 peerSocket = (SSLSocket) doInteraction(connection, ss); 479 checkConnectionStateParameters(connection, peerSocket); 480 } 481 482 /** 483 * Tests HTTPS connection process made through the proxy server. 484 * Proxy server needs authentication. 485 * Client sends data to the server. 486 */ 487 public void testProxyAuthConnection_doOutput() throws Throwable { 488 // setting up the properties pointing to the key/trust stores 489 setUpStoreProperties(); 490 491 // create the SSLServerSocket which will be used by server side 492 ServerSocket ss = new ServerSocket(0); 493 494 // create the HostnameVerifier to check that Hostname verification 495 // is done 496 TestHostnameVerifier hnv = new TestHostnameVerifier(); 497 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 498 499 Authenticator.setDefault(new Authenticator() { 500 501 protected PasswordAuthentication getPasswordAuthentication() { 502 return new PasswordAuthentication("user", "password" 503 .toCharArray()); 504 } 505 }); 506 507 // create HttpsURLConnection to be tested 508 URL url = new URL("https://requested.host:55554/requested.data"); 509 HttpsURLConnection connection = (HttpsURLConnection) 510 url.openConnection(new Proxy(Proxy.Type.HTTP, 511 new InetSocketAddress("localhost", 512 ss.getLocalPort()))); 513 connection.setSSLSocketFactory(getContext().getSocketFactory()); 514 connection.setDoOutput(true); 515 516 // perform the interaction between the peers and check the results 517 SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss, OK_CODE, true); 518 checkConnectionStateParameters(connection, peerSocket); 519 } 520 521 /** 522 * Tests HTTPS connection process made through the proxy server. 523 * Proxy server needs authentication but client fails to authenticate 524 * (Authenticator was not set up in the system). 525 */ 526 public void testProxyAuthConnectionFailed() throws Throwable { 527 // setting up the properties pointing to the key/trust stores 528 setUpStoreProperties(); 529 530 // create the SSLServerSocket which will be used by server side 531 ServerSocket ss = new ServerSocket(0); 532 533 // create the HostnameVerifier to check that Hostname verification 534 // is done 535 TestHostnameVerifier hnv = new TestHostnameVerifier(); 536 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 537 538 // create HttpsURLConnection to be tested 539 URL url = new URL("https://requested.host:55555/requested.data"); 540 HttpsURLConnection connection = (HttpsURLConnection) 541 url.openConnection(new Proxy(Proxy.Type.HTTP, 542 new InetSocketAddress("localhost", 543 ss.getLocalPort()))); 544 connection.setSSLSocketFactory(getContext().getSocketFactory()); 545 546 // perform the interaction between the peers and check the results 547 try { 548 doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE, true); 549 } catch (IOException e) { 550 // SSL Tunnelling failed 551 if (DO_LOG) { 552 System.out.println("Got expected IOException: " 553 + e.getMessage()); 554 } 555 } 556 } 557 558 /** 559 * Tests the behaviour of HTTPS connection in case of unavailability 560 * of requested resource. 561 */ 562 public void testProxyConnection_Not_Found_Response() throws Throwable { 563 // setting up the properties pointing to the key/trust stores 564 setUpStoreProperties(); 565 566 // create the SSLServerSocket which will be used by server side 567 ServerSocket ss = new ServerSocket(0); 568 569 // create the HostnameVerifier to check that Hostname verification 570 // is done 571 TestHostnameVerifier hnv = new TestHostnameVerifier(); 572 HttpsURLConnection.setDefaultHostnameVerifier(hnv); 573 574 // create HttpsURLConnection to be tested 575 URL url = new URL("https://localhost:" + ss.getLocalPort()); 576 HttpsURLConnection connection = (HttpsURLConnection) 577 url.openConnection(new Proxy(Proxy.Type.HTTP, 578 new InetSocketAddress("localhost", 579 ss.getLocalPort()))); 580 connection.setSSLSocketFactory(getContext().getSocketFactory()); 581 582 try { 583 doInteraction(connection, ss, NOT_FOUND_CODE); // NOT FOUND 584 fail("Expected exception was not thrown."); 585 } catch (FileNotFoundException e) { 586 if (DO_LOG) { 587 System.out.println("Expected exception was thrown: " 588 + e.getMessage()); 589 } 590 } 591 } 592 593 /** 594 * Log the name of the test case to be executed. 595 */ 596 public void setUp() throws Exception { 597 super.setUp(); 598 599 if (DO_LOG) { 600 System.out.println(); 601 System.out.println("------------------------"); 602 System.out.println("------ " + getName()); 603 System.out.println("------------------------"); 604 } 605 606 if (store != null) { 607 String ksFileName = ("org/apache/harmony/luni/tests/key_store." 608 + KeyStore.getDefaultType().toLowerCase()); 609 InputStream in = getClass().getClassLoader().getResourceAsStream(ksFileName); 610 FileOutputStream out = new FileOutputStream(store); 611 BufferedInputStream bufIn = new BufferedInputStream(in, 8192); 612 while (bufIn.available() > 0) { 613 byte[] buf = new byte[128]; 614 int read = bufIn.read(buf); 615 out.write(buf, 0, read); 616 } 617 bufIn.close(); 618 out.close(); 619 } else { 620 fail("couldn't set up key store"); 621 } 622 } 623 624 public void tearDown() { 625 if (store != null) { 626 store.delete(); 627 } 628 } 629 630 /** 631 * Checks the HttpsURLConnection getter's values and compares 632 * them with actual corresponding values of remote peer. 633 */ 634 public static void checkConnectionStateParameters( 635 HttpsURLConnection clientConnection, SSLSocket serverPeer) 636 throws Exception { 637 SSLSession session = serverPeer.getSession(); 638 639 assertEquals(session.getCipherSuite(), clientConnection.getCipherSuite()); 640 assertEquals(session.getLocalPrincipal(), clientConnection.getPeerPrincipal()); 641 assertEquals(session.getPeerPrincipal(), clientConnection.getLocalPrincipal()); 642 643 Certificate[] serverCertificates = clientConnection.getServerCertificates(); 644 Certificate[] localCertificates = session.getLocalCertificates(); 645 assertTrue("Server certificates differ from expected", 646 Arrays.equals(serverCertificates, localCertificates)); 647 648 localCertificates = clientConnection.getLocalCertificates(); 649 serverCertificates = session.getPeerCertificates(); 650 assertTrue("Local certificates differ from expected", 651 Arrays.equals(serverCertificates, localCertificates)); 652 } 653 654 /** 655 * Returns the file name of the key/trust store. The key store file 656 * (named as "key_store." + extension equals to the default KeyStore 657 * type installed in the system in lower case) is searched in classpath. 658 * @throws junit.framework.AssertionFailedError if property was not set 659 * or file does not exist. 660 */ 661 private static String getKeyStoreFileName() { 662 return store.getAbsolutePath(); 663 } 664 665 /** 666 * Builds and returns the context used for secure socket creation. 667 */ 668 private static SSLContext getContext() throws Exception { 669 String type = KeyStore.getDefaultType(); 670 String keyStore = getKeyStoreFileName(); 671 File keyStoreFile = new File(keyStore); 672 FileInputStream fis = new FileInputStream(keyStoreFile); 673 674 KeyStore ks = KeyStore.getInstance(type); 675 ks.load(fis, KS_PASSWORD.toCharArray()); 676 fis.close(); 677 if (DO_LOG && false) { 678 TestKeyStore.dump("HttpsURLConnection.getContext", ks, KS_PASSWORD.toCharArray()); 679 } 680 681 String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); 682 KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm); 683 kmf.init(ks, KS_PASSWORD.toCharArray()); 684 KeyManager[] keyManagers = kmf.getKeyManagers(); 685 686 String tmfAlgorthm = TrustManagerFactory.getDefaultAlgorithm(); 687 TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorthm); 688 tmf.init(ks); 689 TrustManager[] trustManagers = tmf.getTrustManagers(); 690 if (DO_LOG) { 691 trustManagers = TestTrustManager.wrap(trustManagers); 692 } 693 694 SSLContext ctx = SSLContext.getInstance("TLSv1"); 695 ctx.init(keyManagers, trustManagers, null); 696 return ctx; 697 } 698 699 /** 700 * Sets up the properties pointing to the key store and trust store 701 * and used as default values by JSSE staff. This is needed to test 702 * HTTPS behaviour in the case of default SSL Socket Factories. 703 */ 704 private static void setUpStoreProperties() throws Exception { 705 String type = KeyStore.getDefaultType(); 706 707 System.setProperty("javax.net.ssl.keyStoreType", type); 708 System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName()); 709 System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD); 710 711 System.setProperty("javax.net.ssl.trustStoreType", type); 712 System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName()); 713 System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD); 714 } 715 716 /** 717 * Performs interaction between client's HttpsURLConnection and 718 * servers side (ServerSocket). 719 */ 720 public static Socket doInteraction(final HttpsURLConnection clientConnection, 721 final ServerSocket serverSocket) 722 throws Throwable { 723 return doInteraction(clientConnection, serverSocket, OK_CODE, false); 724 } 725 726 /** 727 * Performs interaction between client's HttpsURLConnection and 728 * servers side (ServerSocket). Server will response with specified 729 * response code. 730 */ 731 public static Socket doInteraction(final HttpsURLConnection clientConnection, 732 final ServerSocket serverSocket, 733 final int responseCode) 734 throws Throwable { 735 return doInteraction(clientConnection, serverSocket, responseCode, false); 736 } 737 738 /** 739 * Performs interaction between client's HttpsURLConnection and 740 * servers side (ServerSocket). Server will response with specified 741 * response code. 742 * @param doAuthentication specifies 743 * if the server needs client authentication. 744 */ 745 public static Socket doInteraction(final HttpsURLConnection clientConnection, 746 final ServerSocket serverSocket, 747 final int responseCode, 748 final boolean doAuthentication) 749 throws Throwable { 750 // set up the connection 751 clientConnection.setDoInput(true); 752 clientConnection.setConnectTimeout(TIMEOUT); 753 clientConnection.setReadTimeout(TIMEOUT); 754 755 ServerWork server = new ServerWork(serverSocket, responseCode, doAuthentication); 756 757 ClientConnectionWork client = new ClientConnectionWork(clientConnection); 758 759 ExecutorService executorService = Executors.newFixedThreadPool(2); 760 try { 761 Future<Void> serverFuture = executorService.submit(server); 762 Future<Void> clientFuture = executorService.submit(client); 763 764 Throwable t = null; 765 try { 766 serverFuture.get(30, TimeUnit.SECONDS); 767 } catch (ExecutionException e) { 768 t = e.getCause(); 769 } 770 try { 771 clientFuture.get(30, TimeUnit.SECONDS); 772 } catch (ExecutionException e) { 773 // two problems? log the first before overwriting 774 if (t != null) { 775 t.printStackTrace(); 776 } 777 t = e.getCause(); 778 } 779 if (t != null) { 780 throw t; 781 } 782 } catch (ExecutionException e) { 783 throw e.getCause(); 784 } finally { 785 executorService.shutdown(); 786 } 787 788 return server.peerSocket; 789 } 790 791 /** 792 * The host name verifier used in test. 793 */ 794 static class TestHostnameVerifier implements HostnameVerifier { 795 796 boolean verified = false; 797 798 public boolean verify(String hostname, SSLSession session) { 799 if (DO_LOG) { 800 System.out.println("***> verification " + hostname + " " 801 + session.getPeerHost()); 802 } 803 verified = true; 804 return true; 805 } 806 } 807 808 /** 809 * The base class for mock Client and Server. 810 */ 811 static class Work { 812 813 /** 814 * The header of OK HTTP response. 815 */ 816 static final String responseHead = "HTTP/1.1 200 OK\r\n"; 817 818 /** 819 * The response message to be sent to the proxy CONNECT request. 820 */ 821 static final String proxyResponse = responseHead + "\r\n"; 822 823 /** 824 * The content of the response to be sent during HTTPS session. 825 */ 826 static final String httpsResponseContent 827 = "<HTML>\n" 828 + "<HEAD><TITLE>HTTPS Response Content</TITLE></HEAD>\n" 829 + "</HTML>"; 830 831 /** 832 * The tail of the response to be sent during HTTPS session. 833 */ 834 static final String httpsResponseTail 835 = "Content-type: text/html\r\n" 836 + "Content-length: " + httpsResponseContent.length() + "\r\n" 837 + "\r\n" 838 + httpsResponseContent; 839 840 /** 841 * The response requiring client's proxy authentication. 842 */ 843 static final String respAuthenticationRequired 844 = "HTTP/1.0 407 Proxy authentication required\r\n" 845 + "Proxy-authenticate: Basic realm=\"localhost\"\r\n" 846 + "\r\n"; 847 848 /** 849 * The data to be posted by client to the server. 850 */ 851 static final String clientsData = "_.-^ Client's Data ^-._"; 852 853 /** 854 * The print stream used for debug log. 855 * If it is null debug info will not be printed. 856 */ 857 private PrintStream out = System.out; 858 859 /** 860 * Prints log message. 861 */ 862 public synchronized void log(String message) { 863 if (DO_LOG && (out != null)) { 864 out.println("[" + this + "]: " + message); 865 } 866 } 867 } 868 869 /** 870 * The class used for server side works. 871 */ 872 static class ServerWork extends Work implements Callable<Void> { 873 874 // the server socket used for connection 875 private final ServerSocket serverSocket; 876 877 // indicates if the server acts as proxy server 878 private final boolean actAsProxy; 879 880 // indicates if the server needs proxy authentication 881 private final boolean needProxyAuthentication; 882 883 // response code to be send to the client peer 884 private final int responseCode; 885 886 // the socket connected with client peer 887 private Socket peerSocket; 888 889 /** 890 * Creates the thread acting as a server side. 891 * @param serverSocket the server socket to be used during connection 892 * @param responseCode the response code to be sent to the client 893 * @param needProxyAuthentication 894 * indicates if the server needs proxy authentication 895 */ 896 public ServerWork(ServerSocket serverSocket, 897 int responseCode, 898 boolean needProxyAuthentication) { 899 this.serverSocket = serverSocket; 900 this.responseCode = responseCode; 901 this.needProxyAuthentication = needProxyAuthentication; 902 // will act as a proxy server if the specified server socket 903 // is not a secure server socket 904 this.actAsProxy = !(serverSocket instanceof SSLServerSocket); 905 if (!actAsProxy) { 906 // demand client to send its certificate 907 ((SSLServerSocket) serverSocket).setNeedClientAuth(true); 908 } 909 } 910 911 /** 912 * Closes the connection. 913 */ 914 public void closeSocket(Socket socket) { 915 if (socket == null) { 916 return; 917 } 918 try { 919 socket.getInputStream().close(); 920 } catch (IOException e) {} 921 try { 922 socket.getOutputStream().close(); 923 } catch (IOException e) {} 924 try { 925 socket.close(); 926 } catch (IOException e) {} 927 } 928 929 /** 930 * Performs the actual server work. 931 * If some exception occurs during the work it will be 932 * stored in the <code>thrown</code> field. 933 */ 934 public Void call() throws Exception { 935 // the buffer used for reading the messages 936 byte[] buff = new byte[2048]; 937 // the number of bytes read into the buffer 938 try { 939 // configure the server socket to avoid blocking 940 serverSocket.setSoTimeout(TIMEOUT); 941 // accept client connection 942 peerSocket = serverSocket.accept(); 943 // configure the client connection to avoid blocking 944 peerSocket.setSoTimeout(TIMEOUT); 945 log("Client connection ACCEPTED"); 946 947 InputStream is = peerSocket.getInputStream(); 948 OutputStream os = peerSocket.getOutputStream(); 949 950 int num = is.read(buff); 951 if (num == -1) { 952 log("Unexpected EOF"); 953 return null; 954 } 955 956 String message = new String(buff, 0, num); 957 log("Got request:\n" + message); 958 log("------------------"); 959 960 if (!actAsProxy) { 961 // Act as Server (not Proxy) side 962 if (message.startsWith("POST")) { 963 // client connection sent some data 964 log("try to read client data"); 965 String data = message.substring(message.indexOf("\r\n\r\n")+4); 966 log("client's data: '" + data + "'"); 967 // check the received data 968 assertEquals(clientsData, data); 969 } 970 } else { 971 if (needProxyAuthentication) { 972 // Do proxy work 973 log("Authentication required..."); 974 // send Authentication Request 975 os.write(respAuthenticationRequired.getBytes()); 976 // read request 977 num = is.read(buff); 978 if (num == -1) { 979 // this connection was closed, 980 // do clean up and create new one: 981 closeSocket(peerSocket); 982 peerSocket = serverSocket.accept(); 983 peerSocket.setSoTimeout(TIMEOUT); 984 log("New client connection ACCEPTED"); 985 is = peerSocket.getInputStream(); 986 os = peerSocket.getOutputStream(); 987 num = is.read(buff); 988 } 989 message = new String(buff, 0, num); 990 log("Got authenticated request:\n" + message); 991 log("------------------"); 992 // check provided authorization credentials 993 assertTrue("no proxy-authorization credentials: " + message, 994 message.toLowerCase().indexOf("proxy-authorization:") != -1); 995 } 996 997 assertTrue(message.startsWith("CONNECT")); 998 // request for SSL tunnel 999 log("Send proxy response"); 1000 os.write(proxyResponse.getBytes()); 1001 1002 log("Perform SSL Handshake..."); 1003 // create sslSocket acting as a remote server peer 1004 SSLSocket sslSocket = (SSLSocket) 1005 getContext().getSocketFactory().createSocket(peerSocket, 1006 "localhost", 1007 peerSocket.getPort(), 1008 true); // do autoclose 1009 sslSocket.setUseClientMode(false); 1010 // demand client authentication 1011 sslSocket.setNeedClientAuth(true); 1012 sslSocket.startHandshake(); 1013 peerSocket = sslSocket; 1014 is = peerSocket.getInputStream(); 1015 os = peerSocket.getOutputStream(); 1016 1017 // read the HTTP request sent by secure connection 1018 // (HTTPS request) 1019 num = is.read(buff); 1020 message = new String(buff, 0, num); 1021 log("[Remote Server] Request from SSL tunnel:\n" + message); 1022 log("------------------"); 1023 1024 if (message.startsWith("POST")) { 1025 // client connection sent some data 1026 log("[Remote Server] try to read client data"); 1027 String data = message.substring(message.indexOf("\r\n\r\n")+4); 1028 log("[Remote Server] client's data: '" + message + "'"); 1029 // check the received data 1030 assertEquals(clientsData, data); 1031 } 1032 1033 log("[Remote Server] Sending the response by SSL tunnel..."); 1034 } 1035 1036 // send the response with specified response code 1037 os.write(("HTTP/1.1 " + responseCode 1038 + " Message\r\n" + httpsResponseTail).getBytes()); 1039 os.flush(); 1040 os.close(); 1041 log("Work is DONE actAsProxy=" + actAsProxy); 1042 return null; 1043 } finally { 1044 closeSocket(peerSocket); 1045 try { 1046 serverSocket.close(); 1047 } catch (IOException e) {} 1048 } 1049 } 1050 1051 @Override public String toString() { 1052 return actAsProxy ? "Proxy Server" : "Server"; 1053 } 1054 } 1055 1056 /** 1057 * The class used for client side work. 1058 */ 1059 static class ClientConnectionWork extends Work implements Callable<Void> { 1060 1061 // connection to be used to contact the server side 1062 private HttpsURLConnection connection; 1063 1064 /** 1065 * Creates the thread acting as a client side. 1066 * @param connection connection to be used to contact the server side 1067 */ 1068 public ClientConnectionWork(HttpsURLConnection connection) { 1069 this.connection = connection; 1070 log("Created over connection: " + connection.getClass()); 1071 } 1072 1073 /** 1074 * Performs the actual client work. 1075 * If some exception occurs during the work it will be 1076 * stored in the <code>thrown<code> field. 1077 */ 1078 public Void call() throws Exception { 1079 log("Opening the connection to " + connection.getURL()); 1080 connection.connect(); 1081 log("Connection has been ESTABLISHED, using proxy: " + connection.usingProxy()); 1082 if (connection.getDoOutput()) { 1083 log("Posting data"); 1084 // connection configured to post data, do so 1085 connection.getOutputStream().write(clientsData.getBytes()); 1086 } 1087 // read the content of HTTP(s) response 1088 InputStream is = connection.getInputStream(); 1089 log("Input Stream obtained"); 1090 byte[] buff = new byte[2048]; 1091 int num = 0; 1092 int byt = 0; 1093 while ((num < buff.length) && ((byt = is.read()) != -1)) { 1094 buff[num++] = (byte) byt; 1095 } 1096 String message = new String(buff, 0, num); 1097 log("Got content:\n" + message); 1098 log("------------------"); 1099 log("Response code: " + connection.getResponseCode()); 1100 assertEquals(httpsResponseContent, message); 1101 return null; 1102 } 1103 1104 @Override public String toString() { 1105 return "Client Connection"; 1106 } 1107 } 1108} 1109