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