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 18 package org.apache.harmony.luni.tests.internal.net.www.protocol.https; 19 20 import com.google.mockwebserver.Dispatcher; 21 import com.google.mockwebserver.MockResponse; 22 import com.google.mockwebserver.MockWebServer; 23 import com.google.mockwebserver.RecordedRequest; 24 import com.google.mockwebserver.SocketPolicy; 25 26 import java.io.BufferedInputStream; 27 import java.io.File; 28 import java.io.FileInputStream; 29 import java.io.FileNotFoundException; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.OutputStream; 34 import java.net.Authenticator; 35 import java.net.InetSocketAddress; 36 import java.net.PasswordAuthentication; 37 import java.net.Proxy; 38 import java.net.ServerSocket; 39 import java.net.Socket; 40 import java.net.URL; 41 import java.security.KeyStore; 42 import java.security.cert.Certificate; 43 import java.util.Arrays; 44 import java.util.Collections; 45 import java.util.LinkedList; 46 47 import javax.net.ssl.HostnameVerifier; 48 import javax.net.ssl.HttpsURLConnection; 49 import javax.net.ssl.KeyManager; 50 import javax.net.ssl.KeyManagerFactory; 51 import javax.net.ssl.SSLContext; 52 import javax.net.ssl.SSLSession; 53 import javax.net.ssl.SSLSocketFactory; 54 import javax.net.ssl.TrustManager; 55 import javax.net.ssl.TrustManagerFactory; 56 import junit.framework.TestCase; 57 import libcore.java.security.TestKeyStore; 58 import libcore.javax.net.ssl.TestTrustManager; 59 60 /** 61 * Implementation independent test for HttpsURLConnection. 62 */ 63 public 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 */ testGetDefaultSSLSocketFactory()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 testHttpsConnection()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 */ testHttpsConnection_Not_Found_Response()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 */ testSetDefaultSSLSocketFactory()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 */ testSetSSLSocketFactory()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 */ testUnconnectedStateParameters()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 */ testSetHostnameVerifier()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 */ test_doOutput()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 */ testProxyConnection()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 */ testProxyAuthConnection()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 */ testConsequentProxyConnection()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 createProxiedServer(Dispatcher getDispatcher)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 */ testProxyAuthConnection_doOutput()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 */ testProxyAuthConnectionFailed()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 } 614 615 /** 616 * Tests the behaviour of HTTPS connection in case of unavailability of requested resource (as 617 * reported by the target web server). 618 */ testProxyConnection_Not_Found_Response()619 public void testProxyConnection_Not_Found_Response() throws Throwable { 620 // set up the properties pointing to the key/trust stores 621 setUpStoreProperties(); 622 623 SSLContext ctx = getContext(); 624 625 // set the HostnameVerifier required to satisfy SSL - always returns "verified". 626 HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier()); 627 628 // create a server that pretends to be a proxy 629 ProxyConnectDispatcher proxyConnectDispatcher = 630 new ProxyConnectDispatcher(false /* authenticationRequired */); 631 SingleRequestDispatcher notFoundDispatcher = 632 new SingleRequestDispatcher(GET_METHOD, NOT_FOUND_CODE); 633 DelegatingDispatcher delegatingDispatcher = 634 new DelegatingDispatcher(proxyConnectDispatcher, notFoundDispatcher); 635 MockWebServer proxyAndWebServer = createProxyAndWebServer(ctx, delegatingDispatcher); 636 637 // create HttpsURLConnection to be tested 638 URL proxyUrl = proxyAndWebServer.getUrl("/"); 639 InetSocketAddress proxyAddress = new InetSocketAddress("localhost", proxyUrl.getPort()); 640 URL url = new URL("https://requested.host:55555/requested.data"); 641 HttpsURLConnection connection = (HttpsURLConnection) 642 url.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddress)); 643 connection.setSSLSocketFactory(getContext().getSocketFactory()); 644 645 try { 646 executeClientRequest(connection, false /* doOutput */); 647 fail("Expected exception was not thrown."); 648 } catch (FileNotFoundException e) { 649 if (DO_LOG) { 650 System.out.println("Expected exception was thrown: " + e.getMessage()); 651 } 652 } 653 } 654 setUp()655 public void setUp() throws Exception { 656 super.setUp(); 657 658 if (DO_LOG) { 659 // Log the name of the test case to be executed. 660 System.out.println(); 661 System.out.println("------------------------"); 662 System.out.println("------ " + getName()); 663 System.out.println("------------------------"); 664 } 665 666 if (store != null) { 667 String ksFileName = "org/apache/harmony/luni/tests/key_store." + 668 KeyStore.getDefaultType().toLowerCase(); 669 InputStream in = getClass().getClassLoader().getResourceAsStream(ksFileName); 670 FileOutputStream out = new FileOutputStream(store); 671 BufferedInputStream bufIn = new BufferedInputStream(in, 8192); 672 while (bufIn.available() > 0) { 673 byte[] buf = new byte[128]; 674 int read = bufIn.read(buf); 675 out.write(buf, 0, read); 676 } 677 bufIn.close(); 678 out.close(); 679 } else { 680 fail("couldn't set up key store"); 681 } 682 } 683 tearDown()684 public void tearDown() { 685 if (store != null) { 686 store.delete(); 687 } 688 } 689 checkConnectionStateParameters( HttpsURLConnection connection, RecordedRequest request)690 private static void checkConnectionStateParameters( 691 HttpsURLConnection connection, RecordedRequest request) throws Exception { 692 assertEquals(request.getSslCipherSuite(), connection.getCipherSuite()); 693 assertEquals(request.getSslLocalPrincipal(), connection.getPeerPrincipal()); 694 assertEquals(request.getSslPeerPrincipal(), connection.getLocalPrincipal()); 695 696 Certificate[] serverCertificates = connection.getServerCertificates(); 697 Certificate[] localCertificates = request.getSslLocalCertificates(); 698 assertTrue("Server certificates differ from expected", 699 Arrays.equals(serverCertificates, localCertificates)); 700 701 localCertificates = connection.getLocalCertificates(); 702 serverCertificates = request.getSslPeerCertificates(); 703 assertTrue("Local certificates differ from expected", 704 Arrays.equals(serverCertificates, localCertificates)); 705 } 706 707 /** 708 * Returns the file name of the key/trust store. The key store file 709 * (named as "key_store." + extension equals to the default KeyStore 710 * type installed in the system in lower case) is searched in classpath. 711 * @throws junit.framework.AssertionFailedError if property was not set 712 * or file does not exist. 713 */ getKeyStoreFileName()714 private static String getKeyStoreFileName() { 715 return store.getAbsolutePath(); 716 } 717 718 /** 719 * Builds and returns the context used for secure socket creation. 720 */ getContext()721 private static SSLContext getContext() throws Exception { 722 String type = KeyStore.getDefaultType(); 723 String keyStore = getKeyStoreFileName(); 724 File keyStoreFile = new File(keyStore); 725 FileInputStream fis = new FileInputStream(keyStoreFile); 726 727 KeyStore ks = KeyStore.getInstance(type); 728 ks.load(fis, KS_PASSWORD.toCharArray()); 729 fis.close(); 730 if (DO_LOG && false) { 731 TestKeyStore.dump("HttpsURLConnection.getContext", ks, KS_PASSWORD.toCharArray()); 732 } 733 734 String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); 735 KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm); 736 kmf.init(ks, KS_PASSWORD.toCharArray()); 737 KeyManager[] keyManagers = kmf.getKeyManagers(); 738 739 String tmfAlgorthm = TrustManagerFactory.getDefaultAlgorithm(); 740 TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorthm); 741 tmf.init(ks); 742 TrustManager[] trustManagers = tmf.getTrustManagers(); 743 if (DO_LOG) { 744 trustManagers = TestTrustManager.wrap(trustManagers); 745 } 746 747 SSLContext ctx = SSLContext.getInstance("TLSv1"); 748 ctx.init(keyManagers, trustManagers, null); 749 return ctx; 750 } 751 752 /** 753 * Sets up the properties pointing to the key store and trust store 754 * and used as default values by JSSE staff. This is needed to test 755 * HTTPS behaviour in the case of default SSL Socket Factories. 756 */ setUpStoreProperties()757 private static void setUpStoreProperties() throws Exception { 758 String type = KeyStore.getDefaultType(); 759 760 System.setProperty("javax.net.ssl.keyStoreType", type); 761 System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName()); 762 System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD); 763 764 System.setProperty("javax.net.ssl.trustStoreType", type); 765 System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName()); 766 System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD); 767 } 768 769 /** 770 * The host name verifier used in test. 771 */ 772 static class TestHostnameVerifier implements HostnameVerifier { 773 774 boolean verified = false; 775 verify(String hostname, SSLSession session)776 public boolean verify(String hostname, SSLSession session) { 777 if (DO_LOG) { 778 System.out.println("***> verification " + hostname + " " 779 + session.getPeerHost()); 780 } 781 verified = true; 782 return true; 783 } 784 } 785 786 /** 787 * Creates a {@link MockWebServer} that acts as both a proxy and then a web server with the 788 * supplied {@link SSLContext} and {@link Dispatcher}. The dispatcher provided must handle the 789 * CONNECT request/responses and {@link SocketPolicy} needed to simulate the hand-off from proxy 790 * to web server. See {@link HttpsURLConnectionTest.ProxyConnectDispatcher}. 791 */ createProxyAndWebServer(SSLContext ctx, Dispatcher dispatcher)792 private static MockWebServer createProxyAndWebServer(SSLContext ctx, Dispatcher dispatcher) 793 throws IOException { 794 return createServer(ctx, dispatcher, true /* handleProxying */); 795 } 796 797 /** 798 * Creates a {@link MockWebServer} that acts as (only) a web server with the supplied 799 * {@link SSLContext} and {@link Dispatcher}. 800 */ createWebServer(SSLContext ctx, Dispatcher dispatcher)801 private static MockWebServer createWebServer(SSLContext ctx, Dispatcher dispatcher) 802 throws IOException { 803 return createServer(ctx, dispatcher, false /* handleProxying */); 804 } 805 createServer( SSLContext ctx, Dispatcher dispatcher, boolean handleProxying)806 private static MockWebServer createServer( 807 SSLContext ctx, Dispatcher dispatcher, boolean handleProxying) 808 throws IOException { 809 MockWebServer webServer = new MockWebServer(); 810 webServer.useHttps(ctx.getSocketFactory(), handleProxying /* tunnelProxy */); 811 webServer.setDispatcher(dispatcher); 812 webServer.play(); 813 return webServer; 814 } 815 816 /** 817 * A {@link Dispatcher} that has a list of dispatchers to delegate to, each of which will be 818 * used for one request and then discarded. 819 */ 820 private static class DelegatingDispatcher extends Dispatcher { 821 private LinkedList<Dispatcher> delegates = new LinkedList<Dispatcher>(); 822 DelegatingDispatcher(Dispatcher... dispatchers)823 public DelegatingDispatcher(Dispatcher... dispatchers) { 824 addAll(dispatchers); 825 } 826 addAll(Dispatcher... dispatchers)827 private void addAll(Dispatcher... dispatchers) { 828 Collections.addAll(delegates, dispatchers); 829 } 830 831 @Override dispatch(RecordedRequest request)832 public MockResponse dispatch(RecordedRequest request) throws InterruptedException { 833 return delegates.removeFirst().dispatch(request); 834 } 835 836 @Override peek()837 public MockResponse peek() { 838 return delegates.getFirst().peek(); 839 } 840 } 841 842 /** Handles a request for SSL tunnel: Answers with a request to authenticate. */ 843 private static class ProxyConnectAuthFailDispatcher extends Dispatcher { 844 845 @Override dispatch(RecordedRequest request)846 public MockResponse dispatch(RecordedRequest request) throws InterruptedException { 847 assertEquals("CONNECT", request.getMethod()); 848 849 MockResponse response = new MockResponse(); 850 response.setResponseCode(AUTHENTICATION_REQUIRED_CODE); 851 response.addHeader("Proxy-authenticate: Basic realm=\"localhost\""); 852 log("Authentication required. Sending response: " + response); 853 return response; 854 } 855 log(String msg)856 private void log(String msg) { 857 HttpsURLConnectionTest.log("ProxyConnectAuthFailDispatcher", msg); 858 } 859 } 860 861 /** 862 * Handles a request for SSL tunnel: Answers with a success and the socket is upgraded to SSL. 863 */ 864 private static class ProxyConnectDispatcher extends Dispatcher { 865 866 private final boolean authenticationRequired; 867 ProxyConnectDispatcher(boolean authenticationRequired)868 private ProxyConnectDispatcher(boolean authenticationRequired) { 869 this.authenticationRequired = authenticationRequired; 870 } 871 872 @Override dispatch(RecordedRequest request)873 public MockResponse dispatch(RecordedRequest request) throws InterruptedException { 874 if (authenticationRequired) { 875 // check provided authorization credentials 876 assertNotNull("no proxy-authorization credentials: " + request, 877 request.getHeader("proxy-authorization")); 878 log("Got authenticated request:\n" + request); 879 log("------------------"); 880 } 881 882 assertEquals("CONNECT", request.getMethod()); 883 log("Send proxy response"); 884 MockResponse response = new MockResponse(); 885 response.setResponseCode(200); 886 response.setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END); 887 return response; 888 } 889 890 @Override peek()891 public MockResponse peek() { 892 return new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END); 893 } 894 log(String msg)895 private void log(String msg) { 896 HttpsURLConnectionTest.log("ProxyConnectDispatcher", msg); 897 } 898 } 899 900 /** 901 * Handles a request: Answers with a response with a specified status code. 902 * If the {@code expectedMethod} is {@code POST} a hardcoded response body {@link #POST_DATA} 903 * will be included in the response. 904 */ 905 private static class SingleRequestDispatcher extends Dispatcher { 906 907 private final String expectedMethod; 908 private final int responseCode; 909 910 private RecordedRequest lastRequest; 911 SingleRequestDispatcher(String expectedMethod, int responseCode)912 private SingleRequestDispatcher(String expectedMethod, int responseCode) { 913 this.responseCode = responseCode; 914 this.expectedMethod = expectedMethod; 915 } 916 917 @Override dispatch(RecordedRequest request)918 public MockResponse dispatch(RecordedRequest request) throws InterruptedException { 919 if (lastRequest != null) { 920 fail("More than one request received"); 921 } 922 log("Request received: " + request); 923 lastRequest = request; 924 assertEquals(expectedMethod, request.getMethod()); 925 if (POST_METHOD.equals(expectedMethod)) { 926 assertEquals(POST_DATA, request.getUtf8Body()); 927 } 928 929 MockResponse response = new MockResponse(); 930 response.setResponseCode(responseCode); 931 response.setBody(RESPONSE_CONTENT); 932 933 log("Responding with: " + response); 934 return response; 935 } 936 getLastRequest()937 public RecordedRequest getLastRequest() { 938 return lastRequest; 939 } 940 941 @Override peek()942 public MockResponse peek() { 943 return new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_END); 944 } 945 log(String msg)946 private void log(String msg) { 947 HttpsURLConnectionTest.log("SingleRequestDispatcher", msg); 948 } 949 } 950 951 /** 952 * Executes an HTTP request using the supplied connection. If {@code doOutput} is {@code true} 953 * the request made is a POST and the request body sent is {@link #POST_DATA}. 954 * If {@code doOutput} is {@code false} the request made is a GET. The response must be a 955 * success with a body {@link #RESPONSE_CONTENT}. 956 */ executeClientRequest( HttpsURLConnection connection, boolean doOutput)957 private static void executeClientRequest( 958 HttpsURLConnection connection, boolean doOutput) throws IOException { 959 960 // set up the connection 961 connection.setDoInput(true); 962 connection.setConnectTimeout(TIMEOUT); 963 connection.setReadTimeout(TIMEOUT); 964 connection.setDoOutput(doOutput); 965 966 log("Client", "Opening the connection to " + connection.getURL()); 967 connection.connect(); 968 log("Client", "Connection has been ESTABLISHED, using proxy: " + connection.usingProxy()); 969 if (doOutput) { 970 log("Client", "Posting data"); 971 // connection configured to post data, do so 972 OutputStream os = connection.getOutputStream(); 973 os.write(POST_DATA.getBytes()); 974 } 975 // read the content of HTTP(s) response 976 InputStream is = connection.getInputStream(); 977 log("Client", "Input Stream obtained"); 978 byte[] buff = new byte[2048]; 979 int num = 0; 980 int byt; 981 while ((num < buff.length) && ((byt = is.read()) != -1)) { 982 buff[num++] = (byte) byt; 983 } 984 String message = new String(buff, 0, num); 985 log("Client", "Got content:\n" + message); 986 log("Client", "------------------"); 987 log("Client", "Response code: " + connection.getResponseCode()); 988 assertEquals(RESPONSE_CONTENT, message); 989 } 990 991 /** 992 * Prints log message. 993 */ log(String origin, String message)994 public static synchronized void log(String origin, String message) { 995 if (DO_LOG) { 996 System.out.println("[" + origin + "]: " + message); 997 } 998 } 999 } 1000