Home | History | Annotate | Download | only in https
      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      */
    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     }
    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      */
    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 
    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 
    684     public void tearDown() {
    685         if (store != null) {
    686             store.delete();
    687         }
    688     }
    689 
    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      */
    714     private static String getKeyStoreFileName() {
    715         return store.getAbsolutePath();
    716     }
    717 
    718     /**
    719      * Builds and returns the context used for secure socket creation.
    720      */
    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      */
    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 
    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      */
    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      */
    801     private static MockWebServer createWebServer(SSLContext ctx, Dispatcher dispatcher)
    802             throws IOException {
    803         return createServer(ctx, dispatcher, false /* handleProxying */);
    804     }
    805 
    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 
    823         public DelegatingDispatcher(Dispatcher... dispatchers) {
    824             addAll(dispatchers);
    825         }
    826 
    827         private void addAll(Dispatcher... dispatchers) {
    828             Collections.addAll(delegates, dispatchers);
    829         }
    830 
    831         @Override
    832         public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
    833             return delegates.removeFirst().dispatch(request);
    834         }
    835 
    836         @Override
    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
    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 
    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 
    868         private ProxyConnectDispatcher(boolean authenticationRequired) {
    869             this.authenticationRequired = authenticationRequired;
    870         }
    871 
    872         @Override
    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
    891         public MockResponse peek() {
    892             return new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END);
    893         }
    894 
    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 
    912         private SingleRequestDispatcher(String expectedMethod, int responseCode) {
    913             this.responseCode = responseCode;
    914             this.expectedMethod = expectedMethod;
    915         }
    916 
    917         @Override
    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 
    937         public RecordedRequest getLastRequest() {
    938             return lastRequest;
    939         }
    940 
    941         @Override
    942         public MockResponse peek() {
    943             return new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
    944         }
    945 
    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      */
    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      */
    994     public static synchronized void log(String origin, String message) {
    995         if (DO_LOG) {
    996             System.out.println("[" + origin + "]: " + message);
    997         }
    998     }
    999 }
   1000