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