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