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 java.io.BufferedInputStream;
21import java.io.File;
22import java.io.FileInputStream;
23import java.io.FileNotFoundException;
24import java.io.FileOutputStream;
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.OutputStream;
28import java.io.PrintStream;
29import java.net.Authenticator;
30import java.net.InetSocketAddress;
31import java.net.PasswordAuthentication;
32import java.net.Proxy;
33import java.net.ServerSocket;
34import java.net.Socket;
35import java.net.URL;
36import java.security.KeyStore;
37import java.security.cert.Certificate;
38import java.util.Arrays;
39import java.util.concurrent.Callable;
40import java.util.concurrent.ExecutionException;
41import java.util.concurrent.ExecutorService;
42import java.util.concurrent.Executors;
43import java.util.concurrent.Future;
44import java.util.concurrent.TimeUnit;
45import javax.net.ssl.HostnameVerifier;
46import javax.net.ssl.HttpsURLConnection;
47import javax.net.ssl.KeyManager;
48import javax.net.ssl.KeyManagerFactory;
49import javax.net.ssl.SSLContext;
50import javax.net.ssl.SSLServerSocket;
51import javax.net.ssl.SSLSession;
52import javax.net.ssl.SSLSocket;
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 * The test needs certstore file placed in system classpath
63 * and named as "key_store." + the type of the
64 * default KeyStore installed in the system in lower case.
65 * <br>
66 * For example: if default KeyStore type in the system is BKS
67 * (i.e. java.security file sets up the property keystore.type=BKS),
68 * thus classpath should point to the directory with "key_store.bks"
69 * file.
70 * <br>
71 * This certstore file should contain self-signed certificate
72 * generated by keytool utility in a usual way.
73 * <br>
74 * The password to the certstore should be "password" (without quotes).
75 */
76public class HttpsURLConnectionTest extends TestCase {
77
78    // the password to the store
79    private static final String KS_PASSWORD = "password";
80
81    // turn on/off logging
82    private static final boolean DO_LOG = false;
83
84    // read/connection timeout value
85    private static final int TIMEOUT = 5000;
86
87    // OK response code
88    private static final int OK_CODE = 200;
89
90    // Not Found response code
91    private static final int NOT_FOUND_CODE = 404;
92
93    // Proxy authentication required response code
94    private static final int AUTHENTICATION_REQUIRED_CODE = 407;
95
96    private static File store;
97
98    static {
99        try {
100            store = File.createTempFile("key_store", "bks");
101        } catch (Exception e) {
102            // ignore
103        }
104    }
105
106    /**
107     * Checks that HttpsURLConnection's default SSLSocketFactory is operable.
108     */
109    public void testGetDefaultSSLSocketFactory() throws Exception {
110        // set up the properties defining the default values needed by SSL stuff
111        setUpStoreProperties();
112
113        SSLSocketFactory defaultSSLSF = HttpsURLConnection.getDefaultSSLSocketFactory();
114        ServerSocket ss = new ServerSocket(0);
115        Socket s = defaultSSLSF.createSocket("localhost", ss.getLocalPort());
116        ss.accept();
117        s.close();
118        ss.close();
119    }
120
121    public void testHttpsConnection() throws Throwable {
122        // set up the properties defining the default values needed by SSL stuff
123        setUpStoreProperties();
124
125        // create the SSL server socket acting as a server
126        SSLContext ctx = getContext();
127        ServerSocket ss = ctx.getServerSocketFactory().createServerSocket(0);
128
129        // create the HostnameVerifier to check hostname verification
130        TestHostnameVerifier hnv = new TestHostnameVerifier();
131        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
132
133        // create url connection to be tested
134        URL url = new URL("https://localhost:" + ss.getLocalPort());
135        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
136        connection.setSSLSocketFactory(ctx.getSocketFactory());
137
138        // perform the interaction between the peers
139        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
140
141        // check the connection state
142        checkConnectionStateParameters(connection, peerSocket);
143
144        // should silently exit
145        connection.connect();
146    }
147
148    /**
149     * Tests the behaviour of HTTPS connection in case of unavailability
150     * of requested resource.
151     */
152    public void testHttpsConnection_Not_Found_Response() throws Throwable {
153        // set up the properties defining the default values needed by SSL stuff
154        setUpStoreProperties();
155
156        // create the SSL server socket acting as a server
157        SSLContext ctx = getContext();
158        ServerSocket ss = ctx.getServerSocketFactory().createServerSocket(0);
159
160        // create the HostnameVerifier to check hostname verification
161        TestHostnameVerifier hnv = new TestHostnameVerifier();
162        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
163
164        // create url connection to be tested
165        URL url = new URL("https://localhost:" + ss.getLocalPort());
166        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
167        connection.setSSLSocketFactory(ctx.getSocketFactory());
168
169        try {
170            doInteraction(connection, ss, NOT_FOUND_CODE);
171            fail("Expected exception was not thrown.");
172        } catch (FileNotFoundException e) {
173            if (DO_LOG) {
174                System.out.println("Expected exception was thrown: " + e.getMessage());
175                e.printStackTrace();
176            }
177        }
178
179        // should silently exit
180        connection.connect();
181    }
182
183    /**
184     * Tests possibility to set up the default SSLSocketFactory
185     * to be used by HttpsURLConnection.
186     */
187    public void testSetDefaultSSLSocketFactory() throws Throwable {
188        // create the SSLServerSocket which will be used by server side
189        SSLContext ctx = getContext();
190        SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(0);
191
192        SSLSocketFactory socketFactory = (SSLSocketFactory) ctx.getSocketFactory();
193        // set up the factory as default
194        HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);
195        // check the result
196        assertSame("Default SSLSocketFactory differs from expected",
197                   socketFactory, HttpsURLConnection.getDefaultSSLSocketFactory());
198
199        // create the HostnameVerifier to check hostname verification
200        TestHostnameVerifier hnv = new TestHostnameVerifier();
201        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
202
203        // create HttpsURLConnection to be tested
204        URL url = new URL("https://localhost:" + ss.getLocalPort());
205        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
206
207        TestHostnameVerifier hnv_late = new TestHostnameVerifier();
208        // late initialization: should not be used for created connection
209        HttpsURLConnection.setDefaultHostnameVerifier(hnv_late);
210
211        // perform the interaction between the peers
212        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
213        // check the connection state
214        checkConnectionStateParameters(connection, peerSocket);
215        // check the verification process
216        assertTrue("Hostname verification was not done", hnv.verified);
217        assertFalse("Hostname verification should not be done by this verifier",
218                    hnv_late.verified);
219        // check the used SSLSocketFactory
220        assertSame("Default SSLSocketFactory should be used",
221                   HttpsURLConnection.getDefaultSSLSocketFactory(),
222                   connection.getSSLSocketFactory());
223
224        // should silently exit
225        connection.connect();
226    }
227
228    /**
229     * Tests possibility to set up the SSLSocketFactory
230     * to be used by HttpsURLConnection.
231     */
232    public void testSetSSLSocketFactory() throws Throwable {
233        // create the SSLServerSocket which will be used by server side
234        SSLContext ctx = getContext();
235        SSLServerSocket ss = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(0);
236
237        // create the HostnameVerifier to check hostname verification
238        TestHostnameVerifier hnv = new TestHostnameVerifier();
239        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
240
241        // create HttpsURLConnection to be tested
242        URL url = new URL("https://localhost:" + ss.getLocalPort());
243        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
244
245        SSLSocketFactory socketFactory = (SSLSocketFactory) ctx.getSocketFactory();
246        connection.setSSLSocketFactory(socketFactory);
247
248        TestHostnameVerifier hnv_late = new TestHostnameVerifier();
249        // late initialization: should not be used for created connection
250        HttpsURLConnection.setDefaultHostnameVerifier(hnv_late);
251
252        // perform the interaction between the peers
253        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
254        // check the connection state
255        checkConnectionStateParameters(connection, peerSocket);
256        // check the verification process
257        assertTrue("Hostname verification was not done", hnv.verified);
258        assertFalse("Hostname verification should not be done by this verifier",
259                    hnv_late.verified);
260        // check the used SSLSocketFactory
261        assertNotSame("Default SSLSocketFactory should not be used",
262                      HttpsURLConnection.getDefaultSSLSocketFactory(),
263                      connection.getSSLSocketFactory());
264        assertSame("Result differs from expected",
265                   socketFactory, connection.getSSLSocketFactory());
266
267        // should silently exit
268        connection.connect();
269    }
270
271    /**
272     * Tests the behaviour of HttpsURLConnection in case of retrieving
273     * of the connection state parameters before connection has been made.
274     */
275    public void testUnconnectedStateParameters() throws Throwable {
276        // create HttpsURLConnection to be tested
277        URL url = new URL("https://localhost:55555");
278        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
279
280        try {
281            connection.getCipherSuite();
282            fail("Expected IllegalStateException was not thrown");
283        } catch (IllegalStateException e) {}
284        try {
285            connection.getPeerPrincipal();
286            fail("Expected IllegalStateException was not thrown");
287        } catch (IllegalStateException e) {}
288        try {
289            connection.getLocalPrincipal();
290            fail("Expected IllegalStateException was not thrown");
291        } catch (IllegalStateException e) {}
292
293        try {
294            connection.getServerCertificates();
295            fail("Expected IllegalStateException was not thrown");
296        } catch (IllegalStateException e) {}
297        try {
298            connection.getLocalCertificates();
299            fail("Expected IllegalStateException was not thrown");
300        } catch (IllegalStateException e) {}
301    }
302
303    /**
304     * Tests if setHostnameVerifier() method replaces default verifier.
305     */
306    public void testSetHostnameVerifier() throws Throwable {
307        // setting up the properties pointing to the key/trust stores
308        setUpStoreProperties();
309
310        // create the SSLServerSocket which will be used by server side
311        SSLServerSocket ss = (SSLServerSocket)
312                getContext().getServerSocketFactory().createServerSocket(0);
313
314        // create the HostnameVerifier to check that Hostname verification
315        // is done
316        TestHostnameVerifier hnv = new TestHostnameVerifier();
317        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
318
319        // create HttpsURLConnection to be tested
320        URL url = new URL("https://localhost:" + ss.getLocalPort());
321        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
322        connection.setSSLSocketFactory(getContext().getSocketFactory());
323
324        TestHostnameVerifier hnv_late = new TestHostnameVerifier();
325        // replace default verifier
326        connection.setHostnameVerifier(hnv_late);
327
328        // perform the interaction between the peers and check the results
329        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
330        assertTrue("Hostname verification was not done", hnv_late.verified);
331        assertFalse("Hostname verification should not be done by this verifier",
332                    hnv.verified);
333        checkConnectionStateParameters(connection, peerSocket);
334
335        // should silently exit
336        connection.connect();
337    }
338
339    /**
340     * Tests the behaviour in case of sending the data to the server.
341     */
342    public void test_doOutput() throws Throwable {
343        // setting up the properties pointing to the key/trust stores
344        setUpStoreProperties();
345
346        // create the SSLServerSocket which will be used by server side
347        SSLServerSocket ss = (SSLServerSocket)
348                getContext().getServerSocketFactory().createServerSocket(0);
349
350        // create the HostnameVerifier to check that Hostname verification
351        // is done
352        TestHostnameVerifier hnv = new TestHostnameVerifier();
353        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
354
355        // create HttpsURLConnection to be tested
356        URL url = new URL("https://localhost:" + ss.getLocalPort());
357        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
358        connection.setSSLSocketFactory(getContext().getSocketFactory());
359        connection.setDoOutput(true);
360
361        // perform the interaction between the peers and check the results
362        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
363        checkConnectionStateParameters(connection, peerSocket);
364
365        // should silently exit
366        connection.connect();
367    }
368
369    /**
370     * Tests HTTPS connection process made through the proxy server.
371     */
372    public void testProxyConnection() throws Throwable {
373        // setting up the properties pointing to the key/trust stores
374        setUpStoreProperties();
375
376        // create the SSLServerSocket which will be used by server side
377        ServerSocket ss = new ServerSocket(0);
378
379        // create the HostnameVerifier to check that Hostname verification
380        // is done
381        TestHostnameVerifier hnv = new TestHostnameVerifier();
382        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
383
384        // create HttpsURLConnection to be tested
385        URL url = new URL("https://requested.host:55556/requested.data");
386        HttpsURLConnection connection = (HttpsURLConnection)
387                url.openConnection(new Proxy(Proxy.Type.HTTP,
388                                             new InetSocketAddress("localhost",
389                                                                   ss.getLocalPort())));
390        connection.setSSLSocketFactory(getContext().getSocketFactory());
391
392        // perform the interaction between the peers and check the results
393        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
394        checkConnectionStateParameters(connection, peerSocket);
395
396        // should silently exit
397        connection.connect();
398    }
399
400    /**
401     * Tests HTTPS connection process made through the proxy server.
402     * Proxy server needs authentication.
403     */
404    public void testProxyAuthConnection() throws Throwable {
405        // setting up the properties pointing to the key/trust stores
406        setUpStoreProperties();
407
408        // create the SSLServerSocket which will be used by server side
409        ServerSocket ss = new ServerSocket(0);
410
411        // create the HostnameVerifier to check that Hostname verification
412        // is done
413        TestHostnameVerifier hnv = new TestHostnameVerifier();
414        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
415
416        Authenticator.setDefault(new Authenticator() {
417
418            protected PasswordAuthentication getPasswordAuthentication() {
419                return new PasswordAuthentication("user", "password"
420                        .toCharArray());
421            }
422        });
423
424        // create HttpsURLConnection to be tested
425        URL url = new URL("https://requested.host:55555/requested.data");
426        HttpsURLConnection connection = (HttpsURLConnection)
427                url.openConnection(new Proxy(Proxy.Type.HTTP,
428                                             new InetSocketAddress("localhost",
429                                                                   ss.getLocalPort())));
430        connection.setSSLSocketFactory(getContext().getSocketFactory());
431
432        // perform the interaction between the peers and check the results
433        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
434        checkConnectionStateParameters(connection, peerSocket);
435
436        // should silently exit
437        connection.connect();
438    }
439
440    /**
441     * Tests HTTPS connection process made through the proxy server.
442     * 2 HTTPS connections are opened for one URL. For the first time
443     * the connection is opened through one proxy,
444     * for the second time through another.
445     */
446    public void testConsequentProxyConnection() throws Throwable {
447        // setting up the properties pointing to the key/trust stores
448        setUpStoreProperties();
449
450        // create the SSLServerSocket which will be used by server side
451        ServerSocket ss = new ServerSocket(0);
452
453        // create the HostnameVerifier to check that Hostname verification
454        // is done
455        TestHostnameVerifier hnv = new TestHostnameVerifier();
456        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
457
458        // create HttpsURLConnection to be tested
459        URL url = new URL("https://requested.host:55555/requested.data");
460        HttpsURLConnection connection = (HttpsURLConnection)
461                url.openConnection(new Proxy(Proxy.Type.HTTP,
462                                             new InetSocketAddress("localhost",
463                                                                   ss.getLocalPort())));
464        connection.setSSLSocketFactory(getContext().getSocketFactory());
465
466        // perform the interaction between the peers and check the results
467        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss);
468        checkConnectionStateParameters(connection, peerSocket);
469
470        // create another SSLServerSocket which will be used by server side
471        ss = new ServerSocket(0);
472
473        connection = (HttpsURLConnection) url.openConnection(new Proxy(
474                Proxy.Type.HTTP, new InetSocketAddress("localhost", ss.getLocalPort())));
475        connection.setSSLSocketFactory(getContext().getSocketFactory());
476
477        // perform the interaction between the peers and check the results
478        peerSocket = (SSLSocket) doInteraction(connection, ss);
479        checkConnectionStateParameters(connection, peerSocket);
480    }
481
482    /**
483     * Tests HTTPS connection process made through the proxy server.
484     * Proxy server needs authentication.
485     * Client sends data to the server.
486     */
487    public void testProxyAuthConnection_doOutput() throws Throwable {
488        // setting up the properties pointing to the key/trust stores
489        setUpStoreProperties();
490
491        // create the SSLServerSocket which will be used by server side
492        ServerSocket ss = new ServerSocket(0);
493
494        // create the HostnameVerifier to check that Hostname verification
495        // is done
496        TestHostnameVerifier hnv = new TestHostnameVerifier();
497        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
498
499        Authenticator.setDefault(new Authenticator() {
500
501            protected PasswordAuthentication getPasswordAuthentication() {
502                return new PasswordAuthentication("user", "password"
503                        .toCharArray());
504            }
505        });
506
507        // create HttpsURLConnection to be tested
508        URL url = new URL("https://requested.host:55554/requested.data");
509        HttpsURLConnection connection = (HttpsURLConnection)
510                url.openConnection(new Proxy(Proxy.Type.HTTP,
511                                             new InetSocketAddress("localhost",
512                                                                   ss.getLocalPort())));
513        connection.setSSLSocketFactory(getContext().getSocketFactory());
514        connection.setDoOutput(true);
515
516        // perform the interaction between the peers and check the results
517        SSLSocket peerSocket = (SSLSocket) doInteraction(connection, ss, OK_CODE, true);
518        checkConnectionStateParameters(connection, peerSocket);
519    }
520
521    /**
522     * Tests HTTPS connection process made through the proxy server.
523     * Proxy server needs authentication but client fails to authenticate
524     * (Authenticator was not set up in the system).
525     */
526    public void testProxyAuthConnectionFailed() throws Throwable {
527        // setting up the properties pointing to the key/trust stores
528        setUpStoreProperties();
529
530        // create the SSLServerSocket which will be used by server side
531        ServerSocket ss = new ServerSocket(0);
532
533        // create the HostnameVerifier to check that Hostname verification
534        // is done
535        TestHostnameVerifier hnv = new TestHostnameVerifier();
536        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
537
538        // create HttpsURLConnection to be tested
539        URL url = new URL("https://requested.host:55555/requested.data");
540        HttpsURLConnection connection = (HttpsURLConnection)
541                url.openConnection(new Proxy(Proxy.Type.HTTP,
542                                             new InetSocketAddress("localhost",
543                                                                   ss.getLocalPort())));
544        connection.setSSLSocketFactory(getContext().getSocketFactory());
545
546        // perform the interaction between the peers and check the results
547        try {
548            doInteraction(connection, ss, AUTHENTICATION_REQUIRED_CODE, true);
549        } catch (IOException e) {
550            // SSL Tunnelling failed
551            if (DO_LOG) {
552                System.out.println("Got expected IOException: "
553                                   + e.getMessage());
554            }
555        }
556    }
557
558    /**
559     * Tests the behaviour of HTTPS connection in case of unavailability
560     * of requested resource.
561     */
562    public void testProxyConnection_Not_Found_Response() throws Throwable {
563        // setting up the properties pointing to the key/trust stores
564        setUpStoreProperties();
565
566        // create the SSLServerSocket which will be used by server side
567        ServerSocket ss = new ServerSocket(0);
568
569        // create the HostnameVerifier to check that Hostname verification
570        // is done
571        TestHostnameVerifier hnv = new TestHostnameVerifier();
572        HttpsURLConnection.setDefaultHostnameVerifier(hnv);
573
574        // create HttpsURLConnection to be tested
575        URL url = new URL("https://localhost:" + ss.getLocalPort());
576        HttpsURLConnection connection = (HttpsURLConnection)
577                url.openConnection(new Proxy(Proxy.Type.HTTP,
578                                             new InetSocketAddress("localhost",
579                                                                   ss.getLocalPort())));
580        connection.setSSLSocketFactory(getContext().getSocketFactory());
581
582        try {
583            doInteraction(connection, ss, NOT_FOUND_CODE); // NOT FOUND
584            fail("Expected exception was not thrown.");
585        } catch (FileNotFoundException e) {
586            if (DO_LOG) {
587                System.out.println("Expected exception was thrown: "
588                                   + e.getMessage());
589            }
590        }
591    }
592
593    /**
594     * Log the name of the test case to be executed.
595     */
596    public void setUp() throws Exception {
597        super.setUp();
598
599        if (DO_LOG) {
600            System.out.println();
601            System.out.println("------------------------");
602            System.out.println("------ " + getName());
603            System.out.println("------------------------");
604        }
605
606        if (store != null) {
607            String ksFileName = ("org/apache/harmony/luni/tests/key_store."
608                                 + KeyStore.getDefaultType().toLowerCase());
609            InputStream in = getClass().getClassLoader().getResourceAsStream(ksFileName);
610            FileOutputStream out = new FileOutputStream(store);
611            BufferedInputStream bufIn = new BufferedInputStream(in, 8192);
612            while (bufIn.available() > 0) {
613                byte[] buf = new byte[128];
614                int read = bufIn.read(buf);
615                out.write(buf, 0, read);
616            }
617            bufIn.close();
618            out.close();
619        } else {
620            fail("couldn't set up key store");
621        }
622    }
623
624    public void tearDown() {
625        if (store != null) {
626            store.delete();
627        }
628    }
629
630    /**
631     * Checks the HttpsURLConnection getter's values and compares
632     * them with actual corresponding values of remote peer.
633     */
634    public static void checkConnectionStateParameters(
635            HttpsURLConnection clientConnection, SSLSocket serverPeer)
636            throws Exception {
637        SSLSession session = serverPeer.getSession();
638
639        assertEquals(session.getCipherSuite(), clientConnection.getCipherSuite());
640        assertEquals(session.getLocalPrincipal(), clientConnection.getPeerPrincipal());
641        assertEquals(session.getPeerPrincipal(), clientConnection.getLocalPrincipal());
642
643        Certificate[] serverCertificates = clientConnection.getServerCertificates();
644        Certificate[] localCertificates = session.getLocalCertificates();
645        assertTrue("Server certificates differ from expected",
646                   Arrays.equals(serverCertificates, localCertificates));
647
648        localCertificates = clientConnection.getLocalCertificates();
649        serverCertificates = session.getPeerCertificates();
650        assertTrue("Local certificates differ from expected",
651                   Arrays.equals(serverCertificates, localCertificates));
652    }
653
654    /**
655     * Returns the file name of the key/trust store. The key store file
656     * (named as "key_store." + extension equals to the default KeyStore
657     * type installed in the system in lower case) is searched in classpath.
658     * @throws junit.framework.AssertionFailedError if property was not set
659     * or file does not exist.
660     */
661    private static String getKeyStoreFileName() {
662        return store.getAbsolutePath();
663    }
664
665    /**
666     * Builds and returns the context used for secure socket creation.
667     */
668    private static SSLContext getContext() throws Exception {
669        String type = KeyStore.getDefaultType();
670        String keyStore = getKeyStoreFileName();
671        File keyStoreFile = new File(keyStore);
672        FileInputStream fis = new FileInputStream(keyStoreFile);
673
674        KeyStore ks = KeyStore.getInstance(type);
675        ks.load(fis, KS_PASSWORD.toCharArray());
676        fis.close();
677        if (DO_LOG && false) {
678            TestKeyStore.dump("HttpsURLConnection.getContext", ks, KS_PASSWORD.toCharArray());
679        }
680
681        String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
682        KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm);
683        kmf.init(ks, KS_PASSWORD.toCharArray());
684        KeyManager[] keyManagers = kmf.getKeyManagers();
685
686        String tmfAlgorthm = TrustManagerFactory.getDefaultAlgorithm();
687        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorthm);
688        tmf.init(ks);
689        TrustManager[] trustManagers = tmf.getTrustManagers();
690        if (DO_LOG) {
691            trustManagers = TestTrustManager.wrap(trustManagers);
692        }
693
694        SSLContext ctx = SSLContext.getInstance("TLSv1");
695        ctx.init(keyManagers, trustManagers, null);
696        return ctx;
697    }
698
699    /**
700     * Sets up the properties pointing to the key store and trust store
701     * and used as default values by JSSE staff. This is needed to test
702     * HTTPS behaviour in the case of default SSL Socket Factories.
703     */
704    private static void setUpStoreProperties() throws Exception {
705        String type = KeyStore.getDefaultType();
706
707        System.setProperty("javax.net.ssl.keyStoreType", type);
708        System.setProperty("javax.net.ssl.keyStore", getKeyStoreFileName());
709        System.setProperty("javax.net.ssl.keyStorePassword", KS_PASSWORD);
710
711        System.setProperty("javax.net.ssl.trustStoreType", type);
712        System.setProperty("javax.net.ssl.trustStore", getKeyStoreFileName());
713        System.setProperty("javax.net.ssl.trustStorePassword", KS_PASSWORD);
714    }
715
716    /**
717     * Performs interaction between client's HttpsURLConnection and
718     * servers side (ServerSocket).
719     */
720    public static Socket doInteraction(final HttpsURLConnection clientConnection,
721                                       final ServerSocket serverSocket)
722            throws Throwable {
723        return doInteraction(clientConnection, serverSocket, OK_CODE, false);
724    }
725
726    /**
727     * Performs interaction between client's HttpsURLConnection and
728     * servers side (ServerSocket). Server will response with specified
729     * response code.
730     */
731    public static Socket doInteraction(final HttpsURLConnection clientConnection,
732                                       final ServerSocket serverSocket,
733                                       final int responseCode)
734            throws Throwable {
735        return doInteraction(clientConnection, serverSocket, responseCode, false);
736    }
737
738    /**
739     * Performs interaction between client's HttpsURLConnection and
740     * servers side (ServerSocket). Server will response with specified
741     * response code.
742     * @param doAuthentication specifies
743     * if the server needs client authentication.
744     */
745    public static Socket doInteraction(final HttpsURLConnection clientConnection,
746                                       final ServerSocket serverSocket,
747                                       final int responseCode,
748                                       final boolean doAuthentication)
749            throws Throwable {
750        // set up the connection
751        clientConnection.setDoInput(true);
752        clientConnection.setConnectTimeout(TIMEOUT);
753        clientConnection.setReadTimeout(TIMEOUT);
754
755        ServerWork server = new ServerWork(serverSocket, responseCode, doAuthentication);
756
757        ClientConnectionWork client = new ClientConnectionWork(clientConnection);
758
759        ExecutorService executorService = Executors.newFixedThreadPool(2);
760        try {
761            Future<Void> serverFuture = executorService.submit(server);
762            Future<Void> clientFuture = executorService.submit(client);
763
764            Throwable t = null;
765            try {
766                serverFuture.get(30, TimeUnit.SECONDS);
767            } catch (ExecutionException e) {
768                t = e.getCause();
769            }
770            try {
771                clientFuture.get(30, TimeUnit.SECONDS);
772            } catch (ExecutionException e) {
773                // two problems? log the first before overwriting
774                if (t != null) {
775                    t.printStackTrace();
776                }
777                t = e.getCause();
778            }
779            if (t != null) {
780                throw t;
781            }
782        } catch (ExecutionException e) {
783            throw e.getCause();
784        } finally {
785            executorService.shutdown();
786        }
787
788        return server.peerSocket;
789    }
790
791    /**
792     * The host name verifier used in test.
793     */
794    static class TestHostnameVerifier implements HostnameVerifier {
795
796        boolean verified = false;
797
798        public boolean verify(String hostname, SSLSession session) {
799            if (DO_LOG) {
800                System.out.println("***> verification " + hostname + " "
801                                   + session.getPeerHost());
802            }
803            verified = true;
804            return true;
805        }
806    }
807
808    /**
809     * The base class for mock Client and Server.
810     */
811    static class Work {
812
813        /**
814         * The header of OK HTTP response.
815         */
816        static final String responseHead = "HTTP/1.1 200 OK\r\n";
817
818        /**
819         * The response message to be sent to the proxy CONNECT request.
820         */
821        static final String proxyResponse = responseHead + "\r\n";
822
823        /**
824         * The content of the response to be sent during HTTPS session.
825         */
826        static final String httpsResponseContent
827                = "<HTML>\n"
828                + "<HEAD><TITLE>HTTPS Response Content</TITLE></HEAD>\n"
829                + "</HTML>";
830
831        /**
832         * The tail of the response to be sent during HTTPS session.
833         */
834        static final String httpsResponseTail
835                = "Content-type: text/html\r\n"
836                + "Content-length: " + httpsResponseContent.length() + "\r\n"
837                + "\r\n"
838                + httpsResponseContent;
839
840        /**
841         * The response requiring client's proxy authentication.
842         */
843        static final String respAuthenticationRequired
844                = "HTTP/1.0 407 Proxy authentication required\r\n"
845                + "Proxy-authenticate: Basic realm=\"localhost\"\r\n"
846                + "\r\n";
847
848        /**
849         * The data to be posted by client to the server.
850         */
851        static final String clientsData = "_.-^ Client's Data ^-._";
852
853        /**
854         * The print stream used for debug log.
855         * If it is null debug info will not be printed.
856         */
857        private PrintStream out = System.out;
858
859        /**
860         * Prints log message.
861         */
862        public synchronized void log(String message) {
863            if (DO_LOG && (out != null)) {
864                out.println("[" + this + "]: " + message);
865            }
866        }
867    }
868
869    /**
870     * The class used for server side works.
871     */
872    static class ServerWork extends Work implements Callable<Void> {
873
874        // the server socket used for connection
875        private final ServerSocket serverSocket;
876
877        // indicates if the server acts as proxy server
878        private final boolean actAsProxy;
879
880        // indicates if the server needs proxy authentication
881        private final boolean needProxyAuthentication;
882
883        // response code to be send to the client peer
884        private final int responseCode;
885
886        // the socket connected with client peer
887        private Socket peerSocket;
888
889        /**
890         * Creates the thread acting as a server side.
891         * @param serverSocket the server socket to be used during connection
892         * @param responseCode the response code to be sent to the client
893         * @param needProxyAuthentication
894         * indicates if the server needs proxy authentication
895         */
896        public ServerWork(ServerSocket serverSocket,
897                          int responseCode,
898                          boolean needProxyAuthentication) {
899            this.serverSocket = serverSocket;
900            this.responseCode = responseCode;
901            this.needProxyAuthentication = needProxyAuthentication;
902            // will act as a proxy server if the specified server socket
903            // is not a secure server socket
904            this.actAsProxy = !(serverSocket instanceof SSLServerSocket);
905            if (!actAsProxy) {
906                // demand client to send its certificate
907                ((SSLServerSocket) serverSocket).setNeedClientAuth(true);
908            }
909        }
910
911        /**
912         * Closes the connection.
913         */
914        public void closeSocket(Socket socket) {
915            if (socket == null) {
916                return;
917            }
918            try {
919                socket.getInputStream().close();
920            } catch (IOException e) {}
921            try {
922                socket.getOutputStream().close();
923            } catch (IOException e) {}
924            try {
925                socket.close();
926            } catch (IOException e) {}
927        }
928
929        /**
930         * Performs the actual server work.
931         * If some exception occurs during the work it will be
932         * stored in the <code>thrown</code> field.
933         */
934        public Void call() throws Exception {
935            // the buffer used for reading the messages
936            byte[] buff = new byte[2048];
937            // the number of bytes read into the buffer
938            try {
939                // configure the server socket to avoid blocking
940                serverSocket.setSoTimeout(TIMEOUT);
941                // accept client connection
942                peerSocket = serverSocket.accept();
943                // configure the client connection to avoid blocking
944                peerSocket.setSoTimeout(TIMEOUT);
945                log("Client connection ACCEPTED");
946
947                InputStream is = peerSocket.getInputStream();
948                OutputStream os = peerSocket.getOutputStream();
949
950                int num = is.read(buff);
951                if (num == -1) {
952                    log("Unexpected EOF");
953                    return null;
954                }
955
956                String message = new String(buff, 0, num);
957                log("Got request:\n" + message);
958                log("------------------");
959
960                if (!actAsProxy) {
961                    // Act as Server (not Proxy) side
962                    if (message.startsWith("POST")) {
963                        // client connection sent some data
964                        log("try to read client data");
965                        String data = message.substring(message.indexOf("\r\n\r\n")+4);
966                        log("client's data: '" + data + "'");
967                        // check the received data
968                        assertEquals(clientsData, data);
969                    }
970                } else {
971                    if (needProxyAuthentication) {
972                        // Do proxy work
973                        log("Authentication required...");
974                        // send Authentication Request
975                        os.write(respAuthenticationRequired.getBytes());
976                        // read request
977                        num = is.read(buff);
978                        if (num == -1) {
979                            // this connection was closed,
980                            // do clean up and create new one:
981                            closeSocket(peerSocket);
982                            peerSocket = serverSocket.accept();
983                            peerSocket.setSoTimeout(TIMEOUT);
984                            log("New client connection ACCEPTED");
985                            is = peerSocket.getInputStream();
986                            os = peerSocket.getOutputStream();
987                            num = is.read(buff);
988                        }
989                        message = new String(buff, 0, num);
990                        log("Got authenticated request:\n" + message);
991                        log("------------------");
992                        // check provided authorization credentials
993                        assertTrue("no proxy-authorization credentials: " + message,
994                                   message.toLowerCase().indexOf("proxy-authorization:") != -1);
995                    }
996
997                    assertTrue(message.startsWith("CONNECT"));
998                    // request for SSL tunnel
999                    log("Send proxy response");
1000                    os.write(proxyResponse.getBytes());
1001
1002                    log("Perform SSL Handshake...");
1003                    // create sslSocket acting as a remote server peer
1004                    SSLSocket sslSocket = (SSLSocket)
1005                            getContext().getSocketFactory().createSocket(peerSocket,
1006                                                                         "localhost",
1007                                                                         peerSocket.getPort(),
1008                                                                         true); // do autoclose
1009                    sslSocket.setUseClientMode(false);
1010                    // demand client authentication
1011                    sslSocket.setNeedClientAuth(true);
1012                    sslSocket.startHandshake();
1013                    peerSocket = sslSocket;
1014                    is = peerSocket.getInputStream();
1015                    os = peerSocket.getOutputStream();
1016
1017                    // read the HTTP request sent by secure connection
1018                    // (HTTPS request)
1019                    num = is.read(buff);
1020                    message = new String(buff, 0, num);
1021                    log("[Remote Server] Request from SSL tunnel:\n" + message);
1022                    log("------------------");
1023
1024                    if (message.startsWith("POST")) {
1025                        // client connection sent some data
1026                        log("[Remote Server] try to read client data");
1027                        String data = message.substring(message.indexOf("\r\n\r\n")+4);
1028                        log("[Remote Server] client's data: '" + message + "'");
1029                        // check the received data
1030                        assertEquals(clientsData, data);
1031                    }
1032
1033                    log("[Remote Server] Sending the response by SSL tunnel...");
1034                }
1035
1036                // send the response with specified response code
1037                os.write(("HTTP/1.1 " + responseCode
1038                          + " Message\r\n" + httpsResponseTail).getBytes());
1039                os.flush();
1040                os.close();
1041                log("Work is DONE actAsProxy=" + actAsProxy);
1042                return null;
1043            } finally {
1044                closeSocket(peerSocket);
1045                try {
1046                    serverSocket.close();
1047                } catch (IOException e) {}
1048            }
1049        }
1050
1051        @Override public String toString() {
1052            return actAsProxy ? "Proxy Server" : "Server";
1053        }
1054    }
1055
1056    /**
1057     * The class used for client side work.
1058     */
1059    static class ClientConnectionWork extends Work implements Callable<Void> {
1060
1061        // connection to be used to contact the server side
1062        private HttpsURLConnection connection;
1063
1064        /**
1065         * Creates the thread acting as a client side.
1066         * @param connection connection to be used to contact the server side
1067         */
1068        public ClientConnectionWork(HttpsURLConnection connection) {
1069            this.connection = connection;
1070            log("Created over connection: " + connection.getClass());
1071        }
1072
1073        /**
1074         * Performs the actual client work.
1075         * If some exception occurs during the work it will be
1076         * stored in the <code>thrown<code> field.
1077         */
1078        public Void call() throws Exception {
1079            log("Opening the connection to " + connection.getURL());
1080            connection.connect();
1081            log("Connection has been ESTABLISHED, using proxy: " + connection.usingProxy());
1082            if (connection.getDoOutput()) {
1083                log("Posting data");
1084                // connection configured to post data, do so
1085                connection.getOutputStream().write(clientsData.getBytes());
1086            }
1087            // read the content of HTTP(s) response
1088            InputStream is = connection.getInputStream();
1089            log("Input Stream obtained");
1090            byte[] buff = new byte[2048];
1091            int num = 0;
1092            int byt = 0;
1093            while ((num < buff.length) && ((byt = is.read()) != -1)) {
1094                buff[num++] = (byte) byt;
1095            }
1096            String message = new String(buff, 0, num);
1097            log("Got content:\n" + message);
1098            log("------------------");
1099            log("Response code: " + connection.getResponseCode());
1100            assertEquals(httpsResponseContent, message);
1101            return null;
1102        }
1103
1104        @Override public String toString() {
1105            return "Client Connection";
1106        }
1107    }
1108}
1109