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