1/*
2 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24//
25// SunJSSE does not support dynamic system properties, no way to re-use
26// system properties in samevm/agentvm mode.
27//
28
29/*
30 * @test
31 * @bug 7068321
32 * @summary Support TLS Server Name Indication (SNI) Extension in JSSE Server
33 * @library ../SSLEngine ../templates
34 * @build SSLEngineService SSLCapabilities SSLExplorer
35 * @run main/othervm SSLEngineExplorerWithCli
36 */
37
38import javax.net.ssl.*;
39import java.nio.*;
40import java.net.*;
41import java.util.*;
42import java.nio.channels.*;
43
44public class SSLEngineExplorerWithCli extends SSLEngineService {
45
46    /*
47     * =============================================================
48     * Set the various variables needed for the tests, then
49     * specify what tests to run on each side.
50     */
51
52    /*
53     * Should we run the client or server in a separate thread?
54     * Both sides can throw exceptions, but do you have a preference
55     * as to which side should be the main thread.
56     */
57    static boolean separateServerThread = true;
58
59    // Is the server ready to serve?
60    volatile static boolean serverReady = false;
61
62    /*
63     * Turn on SSL debugging?
64     */
65    static boolean debug = false;
66
67    /*
68     * Define the server side of the test.
69     *
70     * If the server prematurely exits, serverReady will be set to true
71     * to avoid infinite hangs.
72     */
73    void doServerSide() throws Exception {
74
75        // create SSLEngine.
76        SSLEngine ssle = createSSLEngine(false);
77
78        // Create a server socket channel.
79        InetSocketAddress isa =
80                new InetSocketAddress(InetAddress.getLocalHost(), serverPort);
81        ServerSocketChannel ssc = ServerSocketChannel.open();
82        ssc.socket().bind(isa);
83        serverPort = ssc.socket().getLocalPort();
84
85        // Signal Client, we're ready for his connect.
86        serverReady = true;
87
88        // Accept a socket channel.
89        SocketChannel sc = ssc.accept();
90
91        // Complete connection.
92        while (!sc.finishConnect()) {
93            Thread.sleep(50);
94            // waiting for the connection completed.
95        }
96
97        ByteBuffer buffer = ByteBuffer.allocate(0xFF);
98        int position = 0;
99        SSLCapabilities capabilities = null;
100
101        // Read the header of TLS record
102        buffer.limit(SSLExplorer.RECORD_HEADER_SIZE);
103        while (position < SSLExplorer.RECORD_HEADER_SIZE) {
104            int n = sc.read(buffer);
105            if (n < 0) {
106                throw new Exception("unexpected end of stream!");
107            }
108            position += n;
109        }
110        buffer.flip();
111
112        int recordLength = SSLExplorer.getRequiredSize(buffer);
113        if (buffer.capacity() < recordLength) {
114            ByteBuffer oldBuffer = buffer;
115            buffer = ByteBuffer.allocate(recordLength);
116            buffer.put(oldBuffer);
117        }
118
119        buffer.position(SSLExplorer.RECORD_HEADER_SIZE);
120        buffer.limit(buffer.capacity());
121        while (position < recordLength) {
122            int n = sc.read(buffer);
123            if (n < 0) {
124                throw new Exception("unexpected end of stream!");
125            }
126            position += n;
127        }
128        buffer.flip();
129
130        capabilities = SSLExplorer.explore(buffer);
131        if (capabilities != null) {
132            System.out.println("Record version: " +
133                    capabilities.getRecordVersion());
134            System.out.println("Hello version: " +
135                    capabilities.getHelloVersion());
136        }
137
138        // handshaking
139        handshaking(ssle, sc, buffer);
140
141        // receive application data
142        receive(ssle, sc);
143
144        // send out application data
145        deliver(ssle, sc);
146
147        ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession();
148        checkCapabilities(capabilities, session);
149
150        // close the socket channel.
151        sc.close();
152        ssc.close();
153    }
154
155    /*
156     * Define the client side of the test.
157     *
158     * If the server prematurely exits, serverReady will be set to true
159     * to avoid infinite hangs.
160     */
161    void doClientSide() throws Exception {
162        // create SSLEngine.
163        SSLEngine ssle = createSSLEngine(true);
164
165        /*
166         * Wait for server to get started.
167         */
168        while (!serverReady) {
169            Thread.sleep(50);
170        }
171
172        // Create a non-blocking socket channel.
173        SocketChannel sc = SocketChannel.open();
174        sc.configureBlocking(false);
175        InetSocketAddress isa =
176                new InetSocketAddress(InetAddress.getLocalHost(), serverPort);
177        sc.connect(isa);
178
179        // Complete connection.
180        while (!sc.finishConnect() ) {
181            Thread.sleep(50);
182            // waiting for the connection completed.
183        }
184
185        SNIHostName serverName = new SNIHostName(clientRequestedHostname);
186        List<SNIServerName> serverNames = new ArrayList<>(1);
187        serverNames.add(serverName);
188        SSLParameters params = ssle.getSSLParameters();
189        params.setServerNames(serverNames);
190        ssle.setSSLParameters(params);
191
192        // handshaking
193        handshaking(ssle, sc, null);
194
195        // send out application data
196        deliver(ssle, sc);
197
198        // receive application data
199        receive(ssle, sc);
200
201        // check server name indication
202        ExtendedSSLSession session = (ExtendedSSLSession)ssle.getSession();
203        checkSNIInSession(session);
204
205        // close the socket channel.
206        sc.close();
207    }
208
209    private static String clientRequestedHostname = "www.example.com";
210    private static String serverAcceptableHostname =
211                                                "www\\.example\\.(com|org)";
212
213    void checkCapabilities(SSLCapabilities capabilities,
214            ExtendedSSLSession session) throws Exception {
215        List<SNIServerName> sessionSNI = session.getRequestedServerNames();
216        if (!sessionSNI.equals(capabilities.getServerNames())) {
217            for (SNIServerName sni : sessionSNI) {
218                System.out.println("SNI in session is " + sni);
219            }
220
221            List<SNIServerName> capaSNI = capabilities.getServerNames();
222            for (SNIServerName sni : capaSNI) {
223                System.out.println("SNI in session is " + sni);
224            }
225
226            throw new Exception(
227                    "server name indication does not match capabilities");
228        }
229
230        checkSNIInSession(session);
231    }
232
233    void checkSNIInSession(ExtendedSSLSession session) throws Exception {
234        List<SNIServerName> sessionSNI = session.getRequestedServerNames();
235        if (sessionSNI.isEmpty()) {
236            throw new Exception(
237                    "unexpected empty request server name indication");
238        }
239
240        if (sessionSNI.size() != 1) {
241            throw new Exception(
242                    "unexpected request server name indication");
243        }
244
245        SNIServerName serverName = sessionSNI.get(0);
246        if (!(serverName instanceof SNIHostName)) {
247            throw new Exception(
248                    "unexpected instance of request server name indication");
249        }
250
251        String hostname = ((SNIHostName)serverName).getAsciiName();
252        if (!clientRequestedHostname.equalsIgnoreCase(hostname)) {
253            throw new Exception(
254                    "unexpected request server name indication value");
255        }
256    }
257
258    /*
259     * =============================================================
260     * The remainder is just support stuff
261     */
262    volatile Exception serverException = null;
263    volatile Exception clientException = null;
264
265    // use any free port by default
266    volatile int serverPort = 0;
267
268    public static void main(String args[]) throws Exception {
269        if (debug)
270            System.setProperty("javax.net.debug", "all");
271
272        new SSLEngineExplorerWithCli();
273    }
274
275    Thread clientThread = null;
276    Thread serverThread = null;
277
278    /*
279     * Primary constructor, used to drive remainder of the test.
280     *
281     * Fork off the other side, then do your work.
282     */
283    SSLEngineExplorerWithCli() throws Exception {
284        super("../etc");
285
286        if (separateServerThread) {
287            startServer(true);
288            startClient(false);
289        } else {
290            startClient(true);
291            startServer(false);
292        }
293
294        /*
295         * Wait for other side to close down.
296         */
297        if (separateServerThread) {
298            serverThread.join();
299        } else {
300            clientThread.join();
301        }
302
303        /*
304         * When we get here, the test is pretty much over.
305         *
306         * If the main thread excepted, that propagates back
307         * immediately.  If the other thread threw an exception, we
308         * should report back.
309         */
310        if (serverException != null) {
311            System.out.print("Server Exception:");
312            throw serverException;
313        }
314        if (clientException != null) {
315            System.out.print("Client Exception:");
316            throw clientException;
317        }
318    }
319
320    void startServer(boolean newThread) throws Exception {
321        if (newThread) {
322            serverThread = new Thread() {
323                public void run() {
324                    try {
325                        doServerSide();
326                    } catch (Exception e) {
327                        /*
328                         * Our server thread just died.
329                         *
330                         * Release the client, if not active already...
331                         */
332                        System.err.println("Server died...");
333                        System.err.println(e);
334                        serverReady = true;
335                        serverException = e;
336                    }
337                }
338            };
339            serverThread.start();
340        } else {
341            doServerSide();
342        }
343    }
344
345    void startClient(boolean newThread) throws Exception {
346        if (newThread) {
347            clientThread = new Thread() {
348                public void run() {
349                    try {
350                        doClientSide();
351                    } catch (Exception e) {
352                        /*
353                         * Our client thread just died.
354                         */
355                        System.err.println("Client died...");
356                        clientException = e;
357                    }
358                }
359            };
360            clientThread.start();
361        } else {
362            doClientSide();
363        }
364    }
365}
366