File: ServerNameRejectedTLSSessionResumption.java

package info (click to toggle)
openjdk-23 23.0.2%2B7-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 815,324 kB
  • sloc: java: 5,632,909; cpp: 1,303,022; xml: 1,237,193; ansic: 419,177; asm: 404,932; objc: 20,978; sh: 15,486; javascript: 11,040; python: 6,802; makefile: 2,331; perl: 357; awk: 351; sed: 172; pascal: 103; exp: 26; jsp: 24; csh: 3
file content (247 lines) | stat: -rw-r--r-- 11,209 bytes parent folder | download | duplicates (10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/*
 * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.nio.ByteBuffer;
import java.util.List;
import java.util.Objects;

import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIMatcher;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.StandardConstants;

/*
 * @test
 * @bug 8301686
 * @summary verifies that if the server rejects session resumption due to SNI
 *          mismatch, during TLS handshake, then the subsequent communication
 *          between the server and the client happens correctly without any
 *          errors
 * @library /javax/net/ssl/templates
 * @run main/othervm -Djavax.net.debug=all
 *                   ServerNameRejectedTLSSessionResumption
 */
public class ServerNameRejectedTLSSessionResumption
        extends SSLContextTemplate {

    private static final String CLIENT_REQUESTED_SNI = "client.local";
    // dummy host, no connection is attempted in this test
    private static final String PEER_HOST = "foobar";
    // dummy port, no connection is attempted in this test
    private static final int PEER_PORT = 12345;

    public static void main(final String[] args) throws Exception {
        new ServerNameRejectedTLSSessionResumption().runTest();
    }

    private void runTest() throws Exception {
        final SSLContext clientSSLContext = createClientSSLContext();
        final SSLContext serverSSLContext = createServerSSLContext();
        // create client and server SSLEngine(s)
        final SSLEngine clientEngine = createClientSSLEngine(clientSSLContext);
        // use a SNIMatcher on the server's SSLEngine which accepts the
        // SNI name presented by the client SSLEngine
        final SSLEngine serverEngine = createServerSSLEngine(serverSSLContext,
                new TestSNIMatcher(CLIENT_REQUESTED_SNI));
        // establish communication, which involves TLS handshake, between the
        // client and server engines. this communication expected to be
        // successful.
        communicate(clientEngine, serverEngine);
        // now that the communication has been successful, we expect the client
        // SSLContext's (internal) cache to have created and cached a
        // SSLSession against the peer host:port

        // now create the SSLEngine(s) again with the same SSLContext
        // instances as before, so that the SSLContext instance attempts
        // to reuse the cached SSLSession against the peer host:port
        final SSLEngine secondClientEngine =
                createClientSSLEngine(clientSSLContext);
        // the newly created SSLEngine for the server will not use any
        // SNIMatcher so as to reject the session resumption (of the
        // cached SSLSession)
        final SSLEngine secondServerEngine =
                createServerSSLEngine(serverSSLContext, null);
        // attempt communication, which again involves TLS handshake
        // since these are new engine instances. The session resumption
        // should be rejected and a fresh session should get created and
        // communication should succeed without any errors
        communicate(secondClientEngine, secondServerEngine);
    }

    private static void communicate(final SSLEngine clientEngine,
                                    final SSLEngine serverEngine)
            throws Exception {

        final ByteBuffer msgFromClient =
                ByteBuffer.wrap("Hi Server, I'm Client".getBytes());
        final ByteBuffer msgFromServer =
                ByteBuffer.wrap("Hello Client, I'm Server".getBytes());
        final ByteBuffer clientBuffer = ByteBuffer.allocate(1 << 15);
        final ByteBuffer serverBuffer = ByteBuffer.allocate(1 << 15);
        /*
         * For data transport, this test uses local ByteBuffers
         */
        final ByteBuffer clientToServerTransport =
                ByteBuffer.allocateDirect(1 << 16);
        final ByteBuffer serverToClientTransport =
                ByteBuffer.allocateDirect(1 << 16);
        boolean isClientToServer = true;
        while (true) {
            if (isClientToServer) {
                // send client's message over the transport, will initiate a
                // TLS handshake if necessary
                SSLEngineResult result = clientEngine.wrap(msgFromClient,
                        clientToServerTransport);
                // run any delegated tasks
                final HandshakeStatus hsStatus = checkAndRunTasks(clientEngine,
                        result.getHandshakeStatus());
                clientToServerTransport.flip(); // will now contain the
                // network data from
                // client to server

                // read from the client generated network data into
                // server's buffer
                result = serverEngine.unwrap(clientToServerTransport,
                        serverBuffer);
                checkAndRunTasks(serverEngine, result.getHandshakeStatus());
                clientToServerTransport.compact();

                if (hsStatus == HandshakeStatus.NEED_UNWRAP) {
                    isClientToServer = false;
                } else if (hsStatus == HandshakeStatus.FINISHED) {
                    break;
                } else if (hsStatus != HandshakeStatus.NEED_WRAP) {
                    throw new Exception("Unexpected handshake result "
                            + result);
                }
            } else {
                // send server's message over the transport
                SSLEngineResult result = serverEngine.wrap(msgFromServer,
                        serverToClientTransport);
                // run any delegated tasks on the server side
                final HandshakeStatus hsStatus = checkAndRunTasks(serverEngine,
                        result.getHandshakeStatus());
                serverToClientTransport.flip(); // will now contain the
                // network data from
                // server to client

                // read from the server generated network data into
                // client's buffer
                result = clientEngine.unwrap(serverToClientTransport,
                        clientBuffer);
                // run any delegated tasks on the client side
                checkAndRunTasks(clientEngine, result.getHandshakeStatus());
                serverToClientTransport.compact();

                if (hsStatus == HandshakeStatus.NEED_UNWRAP) {
                    isClientToServer = true;
                } else if (hsStatus == HandshakeStatus.FINISHED) {
                    break;
                } else if (hsStatus != HandshakeStatus.NEED_WRAP) {
                    throw new Exception("Unexpected handshake result "
                            + result);
                }
            }
        }
        serverEngine.wrap(msgFromServer, serverToClientTransport);
        serverToClientTransport.flip();
        clientEngine.unwrap(serverToClientTransport, clientBuffer);
        serverToClientTransport.compact();
    }

    private static SSLEngine createServerSSLEngine(
            final SSLContext sslContext, final SNIMatcher sniMatcher) {
        final SSLEngine serverEngine = sslContext.createSSLEngine();
        serverEngine.setUseClientMode(false);
        if (sniMatcher != null) {
            final SSLParameters sslParameters =
                    serverEngine.getSSLParameters(); // returns a copy
            sslParameters.setSNIMatchers(List.of(sniMatcher));
            // use the updated params
            serverEngine.setSSLParameters(sslParameters);
        }
        return serverEngine;
    }

    private static SSLEngine createClientSSLEngine(
            final SSLContext sslContext) {
        final SSLEngine clientEngine = sslContext.createSSLEngine(PEER_HOST,
                PEER_PORT);
        clientEngine.setUseClientMode(true);
        final SSLParameters params =
                clientEngine.getSSLParameters(); // returns a copy
        // setup SNI name that will be used by the client during TLS handshake
        params.setServerNames(List.of(new SNIHostName(CLIENT_REQUESTED_SNI)));
        clientEngine.setSSLParameters(params); // use the updated params
        return clientEngine;
    }

    private static HandshakeStatus checkAndRunTasks(
            final SSLEngine engine, final HandshakeStatus handshakeStatus) {
        if (handshakeStatus != HandshakeStatus.NEED_TASK) {
            return handshakeStatus;
        }
        Runnable runnable;
        while ((runnable = engine.getDelegatedTask()) != null) {
            System.out.println("Running task " + runnable);
            runnable.run();
        }
        return engine.getHandshakeStatus();
    }

    private static final class TestSNIMatcher extends SNIMatcher {

        private final String recognizedSNIServerName;

        private TestSNIMatcher(final String recognizedSNIServerName) {
            super(StandardConstants.SNI_HOST_NAME);
            this.recognizedSNIServerName = recognizedSNIServerName;
        }

        @Override
        public boolean matches(final SNIServerName clientRequestedSNI) {
            Objects.requireNonNull(clientRequestedSNI);
            System.out.println("Attempting SNI match against client" +
                    " request SNI name: " + clientRequestedSNI +
                    " against server recognized SNI name "
                    + recognizedSNIServerName);
            if (!SNIHostName.class.isInstance(clientRequestedSNI)) {
                System.out.println("SNI match failed - client request" +
                        " SNI isn't a SNIHostName");
                // we only support SNIHostName type
                return false;
            }
            final String requestedName =
                    ((SNIHostName) clientRequestedSNI).getAsciiName();
            final boolean matches =
                    recognizedSNIServerName.equals(requestedName);
            System.out.println("SNI match " + (matches ? "passed" : "failed"));
            return matches;
        }
    }
}