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