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 com.google.mockwebserver.Dispatcher; 21import com.google.mockwebserver.MockResponse; 22import com.google.mockwebserver.MockWebServer; 23import com.google.mockwebserver.RecordedRequest; 24import com.google.mockwebserver.SocketPolicy; 25 26import java.io.BufferedInputStream; 27import java.io.File; 28import java.io.FileInputStream; 29import java.io.FileNotFoundException; 30import java.io.FileOutputStream; 31import java.io.IOException; 32import java.io.InputStream; 33import java.io.OutputStream; 34import java.net.Authenticator; 35import java.net.InetSocketAddress; 36import java.net.PasswordAuthentication; 37import java.net.Proxy; 38import java.net.ServerSocket; 39import java.net.Socket; 40import java.net.URL; 41import java.security.KeyStore; 42import java.security.cert.Certificate; 43import java.util.Arrays; 44import java.util.Collections; 45import java.util.LinkedList; 46 47import javax.net.ssl.HostnameVerifier; 48import javax.net.ssl.HttpsURLConnection; 49import javax.net.ssl.KeyManager; 50import javax.net.ssl.KeyManagerFactory; 51import javax.net.ssl.SSLContext; 52import javax.net.ssl.SSLSession; 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 */ 63public class HttpsURLConnectionTest extends TestCase { 64 65 private static final String POST_METHOD = "POST"; 66 67 private static final String GET_METHOD = "GET"; 68 69 /** 70 * Data to be posted by client to the server when the method is POST. 71 */ 72 private static final String POST_DATA = "_.-^ Client's Data ^-._"; 73 74 /** 75 * The content of the response to be sent during HTTPS session. 76 */ 77 private static final String RESPONSE_CONTENT 78 = "<HTML>\n" 79 + "<HEAD><TITLE>HTTPS Response Content</TITLE></HEAD>\n" 80 + "</HTML>"; 81 82 // the password to the store 83 private static final String KS_PASSWORD = "password"; 84 85 // turn on/off logging 86 private static final boolean DO_LOG = false; 87 88 // read/connection timeout value 89 private static final int TIMEOUT = 5000; 90 91 // OK response code 92 private static final int OK_CODE = 200; 93 94 // Not Found response code 95 private static final int NOT_FOUND_CODE = 404; 96 97 // Proxy authentication required response code 98 private static final int AUTHENTICATION_REQUIRED_CODE = 407; 99 100 private static File store; 101 102 static { 103 try { 104 store = File.createTempFile("key_store", "bks"); 105 } catch (Exception e) { 106 // ignore 107 } 108 } 109 110 /** 111 * Checks that HttpsURLConnection's default SSLSocketFactory is operable. 112 */ 113 public void testGetDefaultSSLSocketFactory() throws Exception { 114 // set up the properties pointing to the key/trust stores 115 setUpStoreProperties(); 116 117 SSLSocketFactory defaultSSLSF = HttpsURLConnection.getDefaultSSLSocketFactory(); 118 ServerSocket ss = new ServerSocket(0); 119 Socket s = defaultSSLSF.createSocket("localhost", ss.getLocalPort()); 120 ss.accept(); 121 s.close(); 122 ss.close(); 123 } 124 125 public void testHttpsConnection() throws Throwable { 126 // set up the properties pointing to the key/trust stores 127 setUpStoreProperties(); 128 129 SSLContext ctx = getContext(); 130 131 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 132 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 133 134 // create a webserver to check and respond to requests 135 SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); 136 MockWebServer webServer = createWebServer(ctx, dispatcher); 137 138 // create url connection to be tested 139 URL url = webServer.getUrl("/"); 140 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 141 connection.setSSLSocketFactory(ctx.getSocketFactory()); 142 143 // perform the interaction between the peers 144 executeClientRequest(connection, false /* doOutput */); 145 146 checkConnectionStateParameters(connection, dispatcher.getLastRequest()); 147 148 // should silently exit 149 connection.connect(); 150 151 webServer.shutdown(); 152 } 153 154 /** 155 * Tests the behaviour of HTTPS connection in case of unavailability of requested resource. 156 */ 157 public void testHttpsConnection_Not_Found_Response() throws Throwable { 158 // set up the properties pointing to the key/trust stores 159 setUpStoreProperties(); 160 161 SSLContext ctx = getContext(); 162 163 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 164 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 165 166 // create a webserver to check and respond to requests 167 SingleRequestDispatcher dispatcher = 168 new SingleRequestDispatcher(GET_METHOD, NOT_FOUND_CODE); 169 MockWebServer webServer = createWebServer(ctx, dispatcher); 170 171 // create url connection to be tested 172 URL url = webServer.getUrl("/"); 173 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 174 connection.setSSLSocketFactory(ctx.getSocketFactory()); 175 176 try { 177 executeClientRequest(connection, false /* doOutput */); 178 fail("Expected exception was not thrown."); 179 } catch (FileNotFoundException e) { 180 if (DO_LOG) { 181 System.out.println("Expected exception was thrown: " + e.getMessage()); 182 e.printStackTrace(); 183 } 184 } 185 186 // should silently exit 187 connection.connect(); 188 189 webServer.shutdown(); 190 } 191 192 /** 193 * Tests possibility to set up the default SSLSocketFactory to be used by HttpsURLConnection. 194 */ 195 public void testSetDefaultSSLSocketFactory() throws Throwable { 196 // set up the properties pointing to the key/trust stores 197 setUpStoreProperties(); 198 199 SSLContext ctx = getContext(); 200 201 SSLSocketFactory socketFactory = ctx.getSocketFactory(); 202 // set up the factory as default 203 HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); 204 // check the result 205 assertSame("Default SSLSocketFactory differs from expected", 206 socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory()); 207 208 // set the initial default host name verifier. 209 TestHostnameVerifier initialHostnameVerifier = new TestHostnameVerifier(); 210 HttpsURLConnection.setDefaultHostnameVerifier(initialHostnameVerifier); 211 212 // create a webserver to check and respond to requests 213 SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); 214 MockWebServer webServer = createWebServer(ctx, dispatcher); 215 216 // create HttpsURLConnection to be tested 217 URL url = webServer.getUrl("/"); 218 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 219 220 // late initialization: this HostnameVerifier should not be used for created connection 221 TestHostnameVerifier lateHostnameVerifier = new TestHostnameVerifier(); 222 HttpsURLConnection.setDefaultHostnameVerifier(lateHostnameVerifier); 223 224 // perform the interaction between the peers 225 executeClientRequest(connection, false /* doOutput */); 226 checkConnectionStateParameters(connection, dispatcher.getLastRequest()); 227 228 // check the verification process 229 assertTrue("Hostname verification was not done", initialHostnameVerifier.verified); 230 assertFalse("Hostname verification should not be done by this verifier", 231 lateHostnameVerifier.verified); 232 // check the used SSLSocketFactory 233 assertSame("Default SSLSocketFactory should be used", 234 HttpsURLConnection.getDefaultSSLSocketFactory(), 235 connection.getSSLSocketFactory()); 236 237 webServer.shutdown(); 238 } 239 240 /** 241 * Tests 242 * {@link javax.net.ssl.HttpsURLConnection#setSSLSocketFactory(javax.net.ssl.SSLSocketFactory)}. 243 */ 244 public void testSetSSLSocketFactory() throws Throwable { 245 // set up the properties pointing to the key/trust stores 246 SSLContext ctx = getContext(); 247 248 // set the initial default host name verifier. 249 TestHostnameVerifier hostnameVerifier = new TestHostnameVerifier(); 250 HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); 251 252 // create a webserver to check and respond to requests 253 SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); 254 MockWebServer webServer = createWebServer(ctx, dispatcher); 255 256 // create HttpsURLConnection to be tested 257 URL url = webServer.getUrl("/"); 258 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 259 260 // late initialization: should not be used for the created connection. 261 SSLSocketFactory socketFactory = ctx.getSocketFactory(); 262 connection.setSSLSocketFactory(socketFactory); 263 264 // late initialization: should not be used for created connection 265 TestHostnameVerifier lateHostnameVerifier = new TestHostnameVerifier(); 266 HttpsURLConnection.setDefaultHostnameVerifier(lateHostnameVerifier); 267 268 // perform the interaction between the peers 269 executeClientRequest(connection, false /* doOutput */); 270 checkConnectionStateParameters(connection, dispatcher.getLastRequest()); 271 // check the verification process 272 assertTrue("Hostname verification was not done", hostnameVerifier.verified); 273 assertFalse("Hostname verification should not be done by this verifier", 274 lateHostnameVerifier.verified); 275 // check the used SSLSocketFactory 276 assertNotSame("Default SSLSocketFactory should not be used", 277 HttpsURLConnection.getDefaultSSLSocketFactory(), 278 connection.getSSLSocketFactory()); 279 assertSame("Result differs from expected", socketFactory, connection.getSSLSocketFactory()); 280 281 webServer.shutdown(); 282 } 283 284 /** 285 * Tests the behaviour of HttpsURLConnection in case of retrieving 286 * of the connection state parameters before connection has been made. 287 */ 288 public void testUnconnectedStateParameters() throws Throwable { 289 // create HttpsURLConnection to be tested 290 URL url = new URL("https://localhost:55555"); 291 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 292 293 try { 294 connection.getCipherSuite(); 295 fail("Expected IllegalStateException was not thrown"); 296 } catch (IllegalStateException e) {} 297 try { 298 connection.getPeerPrincipal(); 299 fail("Expected IllegalStateException was not thrown"); 300 } catch (IllegalStateException e) {} 301 try { 302 connection.getLocalPrincipal(); 303 fail("Expected IllegalStateException was not thrown"); 304 } catch (IllegalStateException e) {} 305 306 try { 307 connection.getServerCertificates(); 308 fail("Expected IllegalStateException was not thrown"); 309 } catch (IllegalStateException e) {} 310 try { 311 connection.getLocalCertificates(); 312 fail("Expected IllegalStateException was not thrown"); 313 } catch (IllegalStateException e) {} 314 } 315 316 /** 317 * Tests if setHostnameVerifier() method replaces default verifier. 318 */ 319 public void testSetHostnameVerifier() throws Throwable { 320 // set up the properties pointing to the key/trust stores 321 setUpStoreProperties(); 322 323 SSLContext ctx = getContext(); 324 325 TestHostnameVerifier defaultHostnameVerifier = new TestHostnameVerifier(); 326 HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); 327 328 // create a webserver to check and respond to requests 329 SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); 330 MockWebServer webServer = createWebServer(ctx, dispatcher); 331 332 // create HttpsURLConnection to be tested 333 URL url = webServer.getUrl("/"); 334 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 335 connection.setSSLSocketFactory(getContext().getSocketFactory()); 336 337 // replace the default verifier 338 TestHostnameVerifier connectionHostnameVerifier = new TestHostnameVerifier(); 339 connection.setHostnameVerifier(connectionHostnameVerifier); 340 341 // perform the interaction between the peers and check the results 342 executeClientRequest(connection, false /* doOutput */); 343 assertTrue("Hostname verification was not done", connectionHostnameVerifier.verified); 344 assertFalse("Hostname verification should not be done by this verifier", 345 defaultHostnameVerifier.verified); 346 347 checkConnectionStateParameters(connection, dispatcher.getLastRequest()); 348 349 webServer.shutdown(); 350 } 351 352 /** 353 * Tests the behaviour in case of sending the data to the server. 354 */ 355 public void test_doOutput() throws Throwable { 356 // set up the properties pointing to the key/trust stores 357 setUpStoreProperties(); 358 359 SSLContext ctx = getContext(); 360 361 // create a webserver to check and respond to requests 362 SingleRequestDispatcher dispatcher = new SingleRequestDispatcher(POST_METHOD, OK_CODE); 363 MockWebServer webServer = createWebServer(ctx, dispatcher); 364 365 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 366 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 367 368 // create HttpsURLConnection to be tested 369 URL url = webServer.getUrl("/"); 370 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 371 connection.setSSLSocketFactory(getContext().getSocketFactory()); 372 373 // perform the interaction between the peers and check the results 374 executeClientRequest(connection, true /* doOutput */); 375 checkConnectionStateParameters(connection, dispatcher.getLastRequest()); 376 377 // should silently exit 378 connection.connect(); 379 380 webServer.shutdown(); 381 } 382 383 /** 384 * Tests HTTPS connection process made through the proxy server. 385 */ 386 public void testProxyConnection() throws Throwable { 387 // set up the properties pointing to the key/trust stores 388 setUpStoreProperties(); 389 390 SSLContext ctx = getContext(); 391 392 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 393 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 394 395 // create a server that pretends to be both a proxy and then the webserver 396 // request 1: proxy CONNECT, respond with OK 397 ProxyConnectDispatcher proxyConnectDispatcher = 398 new ProxyConnectDispatcher(false /* authenticationRequired */); 399 // request 2: tunnelled GET, respond with OK 400 SingleRequestDispatcher getDispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); 401 DelegatingDispatcher delegatingDispatcher = 402 new DelegatingDispatcher(proxyConnectDispatcher, getDispatcher); 403 MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher); 404 405 // create HttpsURLConnection to be tested 406 URL proxyUrl = proxyAndWebServer.getUrl("/"); 407 InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); 408 URL url = new URL("https://requested.host:55556/requested.data"); 409 HttpsURLConnection connection = (HttpsURLConnection) 410 url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); 411 connection.setSSLSocketFactory(getContext().getSocketFactory()); 412 413 // perform the interaction between the peers and check the results 414 executeClientRequest(connection, false /* doOutput */); 415 checkConnectionStateParameters(connection, getDispatcher.getLastRequest()); 416 417 // should silently exit 418 connection.connect(); 419 420 proxyAndWebServer.shutdown(); 421 } 422 423 /** 424 * Tests HTTPS connection process made through the proxy server. 425 * Proxy server needs authentication. 426 */ 427 public void testProxyAuthConnection() throws Throwable { 428 // set up the properties pointing to the key/trust stores 429 setUpStoreProperties(); 430 431 SSLContext ctx = getContext(); 432 433 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 434 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 435 436 Authenticator.setDefault(new Authenticator() { 437 protected PasswordAuthentication getPasswordAuthentication() { 438 return new PasswordAuthentication("user", "password".toCharArray()); 439 } 440 }); 441 442 // create a server that pretends to be both a proxy and then the webserver 443 // request 1: proxy CONNECT, respond with auth challenge 444 ProxyConnectAuthFailDispatcher authFailDispatcher = new ProxyConnectAuthFailDispatcher(); 445 // request 2: proxy CONNECT, respond with OK 446 ProxyConnectDispatcher proxyConnectDispatcher = 447 new ProxyConnectDispatcher(true /* authenticationRequired */); 448 // request 3: tunnelled GET, respond with OK 449 SingleRequestDispatcher getDispatcher = new SingleRequestDispatcher(GET_METHOD, OK_CODE); 450 DelegatingDispatcher delegatingDispatcher = new DelegatingDispatcher( 451 authFailDispatcher, proxyConnectDispatcher, getDispatcher); 452 MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher); 453 454 // create HttpsURLConnection to be tested 455 URL proxyUrl = proxyAndWebServer.getUrl("/"); 456 InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); 457 URL url = new URL("https://requested.host:55555/requested.data"); 458 HttpsURLConnection connection = (HttpsURLConnection) 459 url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); 460 connection.setSSLSocketFactory(getContext().getSocketFactory()); 461 462 // perform the interaction between the peers and check the results 463 executeClientRequest(connection, false /* doOutput */); 464 checkConnectionStateParameters(connection, getDispatcher.getLastRequest()); 465 466 // should silently exit 467 connection.connect(); 468 469 proxyAndWebServer.shutdown(); 470 } 471 472 /** 473 * Tests HTTPS connection process made through the proxy server. 474 * Two HTTPS connections are opened for one URL: the first time the connection is opened 475 * through one proxy, the second time it is opened through another. 476 */ 477 public void testConsequentProxyConnection() throws Throwable { 478 // set up the properties pointing to the key/trust stores 479 setUpStoreProperties(); 480 481 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 482 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 483 484 // create a server that pretends to be both a proxy and then the webserver 485 SingleRequestDispatcher getDispatcher1 = new SingleRequestDispatcher(GET_METHOD, OK_CODE); 486 MockWebServer proxyAndWebServer1 = createProxiedServer(getDispatcher1); 487 488 // create HttpsURLConnection to be tested 489 URL proxyUrl1 = proxyAndWebServer1.getUrl("/"); 490 URL url = new URL("https://requested.host:55555/requested.data"); 491 InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl1.getPort()); 492 HttpsURLConnection connection = (HttpsURLConnection) 493 url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); 494 connection.setSSLSocketFactory(getContext().getSocketFactory()); 495 executeClientRequest(connection, false /* doOutput */); 496 checkConnectionStateParameters(connection, getDispatcher1.getLastRequest()); 497 498 proxyAndWebServer1.shutdown(); 499 500 // create another server 501 SingleRequestDispatcher getDispatcher2 = new SingleRequestDispatcher(GET_METHOD, OK_CODE); 502 MockWebServer proxyAndWebServer2 = createProxiedServer(getDispatcher2); 503 504 // create another HttpsURLConnection to be tested 505 URL proxyUrl2 = proxyAndWebServer2.getUrl("/"); 506 InetSocketAddress proxyAddress2 = new InetSocketAddress("localhost", proxyUrl2.getPort()); 507 HttpsURLConnection connection2 = (HttpsURLConnection) url.openConnection( 508 new Proxy(Proxy.Type.HTTP, proxyAddress2)); 509 connection2.setSSLSocketFactory(getContext().getSocketFactory()); 510 511 // perform the interaction between the peers and check the results 512 executeClientRequest(connection2, false /* doOutput */); 513 checkConnectionStateParameters(connection2, getDispatcher2.getLastRequest()); 514 515 proxyAndWebServer2.shutdown(); 516 } 517 518 private static MockWebServer createProxiedServer(Dispatcher getDispatcher) 519 throws Exception { 520 // request 1: proxy CONNECT, respond with OK 521 ProxyConnectDispatcher proxyConnectDispatcher = 522 new ProxyConnectDispatcher(false /* authenticationRequired */); 523 // request 2: The get dispatcher. 524 DelegatingDispatcher delegatingDispatcher1 = 525 new DelegatingDispatcher(proxyConnectDispatcher, getDispatcher); 526 return createProxyAndWebServer(getContext(), delegatingDispatcher1); 527 } 528 529 /** 530 * Tests HTTPS connection process made through the proxy server. 531 * Proxy server needs authentication. 532 * Client sends data to the server. 533 */ 534 public void testProxyAuthConnection_doOutput() throws Throwable { 535 // set up the properties pointing to the key/trust stores 536 setUpStoreProperties(); 537 538 SSLContext ctx = getContext(); 539 540 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 541 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 542 543 Authenticator.setDefault(new Authenticator() { 544 protected PasswordAuthentication getPasswordAuthentication() { 545 return new PasswordAuthentication("user", "password".toCharArray()); 546 } 547 }); 548 549 // create a server that pretends to be both a proxy and then the webserver 550 // request 1: proxy CONNECT, respond with auth challenge 551 ProxyConnectAuthFailDispatcher authFailDispatcher = new ProxyConnectAuthFailDispatcher(); 552 // request 2: proxy CONNECT, respond with OK 553 ProxyConnectDispatcher proxyConnectDispatcher = 554 new ProxyConnectDispatcher(true /* authenticationRequired */); 555 // request 3: tunnelled POST, respond with OK 556 SingleRequestDispatcher postDispatcher = new SingleRequestDispatcher(POST_METHOD, OK_CODE); 557 DelegatingDispatcher delegatingDispatcher = new DelegatingDispatcher( 558 authFailDispatcher, proxyConnectDispatcher, postDispatcher); 559 MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher); 560 URL proxyUrl = proxyAndWebServer.getUrl("/"); 561 562 // create HttpsURLConnection to be tested 563 InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); 564 HttpsURLConnection connection = (HttpsURLConnection) 565 proxyUrl.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); 566 connection.setSSLSocketFactory(getContext().getSocketFactory()); 567 568 // perform the interaction between the peers and check the results 569 executeClientRequest(connection, true /* doOutput */); 570 checkConnectionStateParameters(connection, postDispatcher.getLastRequest()); 571 572 // should silently exit 573 connection.connect(); 574 575 proxyAndWebServer.shutdown(); 576 } 577 578 /** 579 * Tests HTTPS connection process made through the proxy server. 580 * Proxy server needs authentication but client fails to authenticate 581 * (Authenticator was not set up in the system). 582 */ 583 public void testProxyAuthConnectionFailed() throws Throwable { 584 // set up the properties pointing to the key/trust stores 585 setUpStoreProperties(); 586 587 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 588 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 589 590 // create a server that pretends to be both a proxy that requests authentication. 591 MockWebServer proxyAndWebServer = new MockWebServer(); 592 ProxyConnectAuthFailDispatcher authFailDispatcher = new ProxyConnectAuthFailDispatcher(); 593 proxyAndWebServer.setDispatcher(authFailDispatcher); 594 proxyAndWebServer.play(); 595 596 // create HttpsURLConnection to be tested 597 URL proxyUrl = proxyAndWebServer.getUrl("/"); 598 InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); 599 URL url = new URL("https://requested.host:55555/requested.data"); 600 HttpsURLConnection connection = (HttpsURLConnection) 601 url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); 602 connection.setSSLSocketFactory(getContext().getSocketFactory()); 603 604 // perform the interaction between the peers and check the results 605 try { 606 executeClientRequest(connection, false); 607 } catch (IOException e) { 608 // SSL Tunnelling failed 609 if (DO_LOG) { 610 System.out.println("Got expected IOException: " + e.getMessage()); 611 } 612 } 613 proxyAndWebServer.shutdown(); 614 } 615 616 /** 617 * Tests the behaviour of HTTPS connection in case of unavailability of requested resource (as 618 * reported by the target web server). 619 */ 620 public void testProxyConnection_Not_Found_Response() throws Throwable { 621 // set up the properties pointing to the key/trust stores 622 setUpStoreProperties(); 623 624 SSLContext ctx = getContext(); 625 626 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 627 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 628 629 // create a server that pretends to be a proxy 630 ProxyConnectDispatcher proxyConnectDispatcher = 631 new ProxyConnectDispatcher(false /* authenticationRequired */); 632 SingleRequestDispatcher notFoundDispatcher = 633 new SingleRequestDispatcher(GET_METHOD, NOT_FOUND_CODE); 634 DelegatingDispatcher delegatingDispatcher = 635 new DelegatingDispatcher(proxyConnectDispatcher, notFoundDispatcher); 636 MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher); 637 638 // create HttpsURLConnection to be tested 639 URL proxyUrl = proxyAndWebServer.getUrl("/"); 640 InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); 641 URL url = new URL("https://requested.host:55555/requested.data"); 642 HttpsURLConnection connection = (HttpsURLConnection) 643 url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); 644 connection.setSSLSocketFactory(getContext().getSocketFactory()); 645 646 try { 647 executeClientRequest(connection, false /* doOutput */); 648 fail("Expected exception was not thrown."); 649 } catch (FileNotFoundException e) { 650 if (DO_LOG) { 651 System.out.println("Expected exception was thrown: " + e.getMessage()); 652 } 653 } 654 proxyAndWebServer.shutdown(); 655 } 656 657 public void setUp() throws Exception { 658 super.setUp(); 659 660 if (DO_LOG) { 661 // Log the name of the test case to be executed. 662 System.out.println(); 663 System.out.println("------------------------"); 664 System.out.println("------ " + getName()); 665 System.out.println("------------------------"); 666 } 667 668 if (store != null) { 669 String ksFileName = "org/apache/harmony/luni/tests/key_store." + 670 KeyStore.getDefaultType().toLowerCase(); 671 InputStream in = getClass().getClassLoader().getResourceAsStream(ksFileName); 672 FileOutputStream out = new FileOutputStream(store); 673 BufferedInputStream bufIn = new BufferedInputStream(in, 8192); 674 while (bufIn.available() > 0) { 675 byte[] buf = new byte[128]; 676 int read = bufIn.read(buf); 677 out.write(buf, 0, read); 678 } 679 bufIn.close(); 680 out.close(); 681 } else { 682 fail("couldn't set up key store"); 683 } 684 } 685 686 public void tearDown() { 687 if (store != null) { 688 store.delete(); 689 } 690 } 691 692 private static void checkConnectionStateParameters( 693 HttpsURLConnection connection, RecordedRequest request) throws Exception { 694 assertEquals(request.getSslCipherSuite(), connection.getCipherSuite()); 695 assertEquals(request.getSslLocalPrincipal(), connection.getPeerPrincipal()); 696 assertEquals(request.getSslPeerPrincipal(), connection.getLocalPrincipal()); 697 698 Certificate[] serverCertificates = connection.getServerCertificates(); 699 Certificate[] localCertificates = request.getSslLocalCertificates(); 700 assertTrue("Server certificates differ from expected", 701 Arrays.equals(serverCertificates, localCertificates)); 702 703 localCertificates = connection.getLocalCertificates(); 704 serverCertificates = request.getSslPeerCertificates(); 705 assertTrue("Local certificates differ from expected", 706 Arrays.equals(serverCertificates, localCertificates)); 707 } 708 709 /** 710 * Returns the file name of the key/trust store. The key store file 711 * (named as "key_store." + extension equals to the default KeyStore 712 * type installed in the system in lower case) is searched in classpath. 713 * @throws junit.framework.AssertionFailedError if property was not set 714 * or file does not exist. 715 */ 716 private static String getKeyStoreFileName() { 717 return store.getAbsolutePath(); 718 } 719 720 /** 721 * Builds and returns the context used for secure socket creation. 722 */ 723 private static SSLContext getContext() throws Exception { 724 String type = KeyStore.getDefaultType(); 725 String keyStore = getKeyStoreFileName(); 726 File keyStoreFile = new File(keyStore); 727 FileInputStream fis = new FileInputStream(keyStoreFile); 728 729 KeyStore ks = KeyStore.getInstance(type); 730 ks.load(fis, KS_PASSWORD.toCharArray()); 731 fis.close(); 732 if (DO_LOG && false) { 733 TestKeyStore.dump("HttpsURLConnection.getContext", ks, KS_PASSWORD.toCharArray()); 734 } 735 736 String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); 737 KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm); 738 kmf.init(ks, KS_PASSWORD.toCharArray()); 739 KeyManager[] keyManagers = kmf.getKeyManagers(); 740 741 String tmfAlgorthm = TrustManagerFactory.getDefaultAlgorithm(); 742 TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorthm); 743 tmf.init(ks); 744 TrustManager[] trustManagers = tmf.getTrustManagers(); 745 if (DO_LOG) { 746 trustManagers = TestTrustManager.wrap(trustManagers); 747 } 748 749 SSLContext ctx = SSLContext.getInstance("TLSv1"); 750 ctx.init(keyManagers, trustManagers, null); 751 return ctx; 752 } 753 754 /** 755 * Sets up the properties pointing to the key store and trust store 756 * and used as default values by JSSE staff. This is needed to test 757 * HTTPS behaviour in the case of default SSL Socket Factories. 758 */ 759 private static void setUpStoreProperties() throws Exception { 760 String type = KeyStore.getDefaultType(); 761 762 System.setProperty("javax.net.ssl.keyStoreType", type); 763 System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName()); 764 System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD); 765 766 System.setProperty("javax.net.ssl.trustStoreType", type); 767 System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName()); 768 System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD); 769 } 770 771 /** 772 * The host name verifier used in test. 773 */ 774 static class TestHostnameVerifier implements HostnameVerifier { 775 776 boolean verified = false; 777 778 public boolean verify(String hostname, SSLSession session) { 779 if (DO_LOG) { 780 System.out.println("***> verification " + hostname + " " 781 + session.getPeerHost()); 782 } 783 verified = true; 784 return true; 785 } 786 } 787 788 /** 789 * Creates a {@link MockWebServer} that acts as both a proxy and then a web server with the 790 * supplied {@link SSLContext} and {@link Dispatcher}. The dispatcher provided must handle the 791 * CONNECT request/responses and {@link SocketPolicy} needed to simulate the hand-off from proxy 792 * to web server. See {@link HttpsURLConnectionTest.ProxyConnectDispatcher}. 793 */ 794 private static MockWebServer createProxyAndWebServer(SSLContext ctx, Dispatcher dispatcher) 795 throws IOException { 796 return createServer(ctx, dispatcher, true /* handleProxying */); 797 } 798 799 /** 800 * Creates a {@link MockWebServer} that acts as (only) a web server with the supplied 801 * {@link SSLContext} and {@link Dispatcher}. 802 */ 803 private static MockWebServer createWebServer(SSLContext ctx, Dispatcher dispatcher) 804 throws IOException { 805 return createServer(ctx, dispatcher, false /* handleProxying */); 806 } 807 808 private static MockWebServer createServer( 809 SSLContext ctx, Dispatcher dispatcher, boolean handleProxying) 810 throws IOException { 811 MockWebServer webServer = new MockWebServer(); 812 webServer.useHttps(ctx.getSocketFactory(), handleProxying /* tunnelProxy */); 813 webServer.setDispatcher(dispatcher); 814 webServer.play(); 815 return webServer; 816 } 817 818 /** 819 * A {@link Dispatcher} that has a list of dispatchers to delegate to, each of which will be 820 * used for one request and then discarded. 821 */ 822 private static class DelegatingDispatcher extends Dispatcher { 823 private LinkedList<Dispatcher> delegates = new LinkedList<Dispatcher>(); 824 825 public DelegatingDispatcher(Dispatcher... dispatchers) { 826 addAll(dispatchers); 827 } 828 829 private void addAll(Dispatcher... dispatchers) { 830 Collections.addAll(delegates, dispatchers); 831 } 832 833 @Override 834 public MockResponse dispatch(RecordedRequest request) throws InterruptedException { 835 return delegates.removeFirst().dispatch(request); 836 } 837 838 @Override 839 public MockResponse peek() { 840 return delegates.getFirst().peek(); 841 } 842 } 843 844 /** Handles a request for SSL tunnel: Answers with a request to authenticate. */ 845 private static class ProxyConnectAuthFailDispatcher extends Dispatcher { 846 847 @Override 848 public MockResponse dispatch(RecordedRequest request) throws InterruptedException { 849 assertEquals("CONNECT", request.getMethod()); 850 851 MockResponse response = new MockResponse(); 852 response.setResponseCode(AUTHENTICATION_REQUIRED_CODE); 853 response.addHeader("Proxy-authenticate: Basic realm=\"localhost\""); 854 log("Authentication required. Sending response: " + response); 855 return response; 856 } 857 858 private void log(String msg) { 859 HttpsURLConnectionTest.log("ProxyConnectAuthFailDispatcher", msg); 860 } 861 } 862 863 /** 864 * Handles a request for SSL tunnel: Answers with a success and the socket is upgraded to SSL. 865 */ 866 private static class ProxyConnectDispatcher extends Dispatcher { 867 868 private final boolean authenticationRequired; 869 870 private ProxyConnectDispatcher(boolean authenticationRequired) { 871 this.authenticationRequired = authenticationRequired; 872 } 873 874 @Override 875 public MockResponse dispatch(RecordedRequest request) throws InterruptedException { 876 if (authenticationRequired) { 877 // check provided authorization credentials 878 assertNotNull("no proxy-authorization credentials: " + request, 879 request.getHeader("proxy-authorization")); 880 log("Got authenticated request:\n" + request); 881 log("------------------"); 882 } 883 884 assertEquals("CONNECT", request.getMethod()); 885 log("Send proxy response"); 886 MockResponse response = new MockResponse(); 887 response.setResponseCode(200); 888 response.setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END); 889 return response; 890 } 891 892 @Override 893 public MockResponse peek() { 894 return new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END); 895 } 896 897 private void log(String msg) { 898 HttpsURLConnectionTest.log("ProxyConnectDispatcher", msg); 899 } 900 } 901 902 /** 903 * Handles a request: Answers with a response with a specified status code. 904 * If the {@code expectedMethod} is {@code POST} a hardcoded response body {@link #POST_DATA} 905 * will be included in the response. 906 */ 907 private static class SingleRequestDispatcher extends Dispatcher { 908 909 private final String expectedMethod; 910 private final int responseCode; 911 912 private RecordedRequest lastRequest; 913 914 private SingleRequestDispatcher(String expectedMethod, int responseCode) { 915 this.responseCode = responseCode; 916 this.expectedMethod = expectedMethod; 917 } 918 919 @Override 920 public MockResponse dispatch(RecordedRequest request) throws InterruptedException { 921 if (lastRequest != null) { 922 fail("More than one request received"); 923 } 924 log("Request received: " + request); 925 lastRequest = request; 926 assertEquals(expectedMethod, request.getMethod()); 927 if (POST_METHOD.equals(expectedMethod)) { 928 assertEquals(POST_DATA, request.getUtf8Body()); 929 } 930 931 MockResponse response = new MockResponse(); 932 response.setResponseCode(responseCode); 933 response.setBody(RESPONSE_CONTENT); 934 935 log("Responding with: " + response); 936 return response; 937 } 938 939 public RecordedRequest getLastRequest() { 940 return lastRequest; 941 } 942 943 @Override 944 public MockResponse peek() { 945 return new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_END); 946 } 947 948 private void log(String msg) { 949 HttpsURLConnectionTest.log("SingleRequestDispatcher", msg); 950 } 951 } 952 953 /** 954 * Executes an HTTP request using the supplied connection. If {@code doOutput} is {@code true} 955 * the request made is a POST and the request body sent is {@link #POST_DATA}. 956 * If {@code doOutput} is {@code false} the request made is a GET. The response must be a 957 * success with a body {@link #RESPONSE_CONTENT}. 958 */ 959 private static void executeClientRequest( 960 HttpsURLConnection connection, boolean doOutput) throws IOException { 961 962 // set up the connection 963 connection.setDoInput(true); 964 connection.setConnectTimeout(TIMEOUT); 965 connection.setReadTimeout(TIMEOUT); 966 connection.setDoOutput(doOutput); 967 968 log("Client", "Opening the connection to " + connection.getURL()); 969 connection.connect(); 970 log("Client", "Connection has been ESTABLISHED, using proxy: " + connection.usingProxy()); 971 if (doOutput) { 972 log("Client", "Posting data"); 973 // connection configured to post data, do so 974 OutputStream os = connection.getOutputStream(); 975 os.write(POST_DATA.getBytes()); 976 } 977 // read the content of HTTP(s) response 978 InputStream is = connection.getInputStream(); 979 log("Client", "Input Stream obtained"); 980 byte[] buff = new byte[2048]; 981 int num = 0; 982 int byt; 983 while ((num < buff.length) && ((byt = is.read()) != -1)) { 984 buff[num++] = (byte) byt; 985 } 986 String message = new String(buff, 0, num); 987 log("Client", "Got content:\n" + message); 988 log("Client", "------------------"); 989 log("Client", "Response code: " + connection.getResponseCode()); 990 assertEquals(RESPONSE_CONTENT, message); 991 } 992 993 /** 994 * Prints log message. 995 */ 996 public static synchronized void log(String origin, String message) { 997 if (DO_LOG) { 998 System.out.println("[" + origin + "]: " + message); 999 } 1000 } 1001} 1002