1 /*
2  * Copyright (c) 2020, 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 // SunJSSE does not support dynamic system properties, no way to re-use
25 // system properties in samevm/agentvm mode.
26 
27 /*
28  * @test
29  * @bug 8254631
30  * @summary Better support ALPN byte wire values in SunJSSE
31  * @library /javax/net/ssl/templates
32  * @run main/othervm AlpnGreaseTest
33  */
34 import javax.net.ssl.*;
35 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
36 import java.nio.ByteBuffer;
37 import java.nio.charset.StandardCharsets;
38 import java.util.Arrays;
39 
40 /**
41  * A SSLEngine usage example which simplifies the presentation
42  * by removing the I/O and multi-threading concerns.
43  *
44  * The test creates two SSLEngines, simulating a client and server.
45  * The "transport" layer consists two byte buffers:  think of them
46  * as directly connected pipes.
47  *
48  * Note, this is a *very* simple example: real code will be much more
49  * involved.  For example, different threading and I/O models could be
50  * used, transport mechanisms could close unexpectedly, and so on.
51  *
52  * When this application runs, notice that several messages
53  * (wrap/unwrap) pass before any application data is consumed or
54  * produced.
55  */
56 public class AlpnGreaseTest implements SSLContextTemplate {
57 
58     private final SSLEngine clientEngine;     // client Engine
59     private final ByteBuffer clientOut;       // write side of clientEngine
60     private final ByteBuffer clientIn;        // read side of clientEngine
61 
62     private final SSLEngine serverEngine;     // server Engine
63     private final ByteBuffer serverOut;       // write side of serverEngine
64     private final ByteBuffer serverIn;        // read side of serverEngine
65 
66     // For data transport, this example uses local ByteBuffers.  This
67     // isn't really useful, but the purpose of this example is to show
68     // SSLEngine concepts, not how to do network transport.
69     private final ByteBuffer cTOs;      // "reliable" transport client->server
70     private final ByteBuffer sTOc;      // "reliable" transport server->client
71 
72     // These are the various 8-bit char values that could be sent as GREASE
73     // values.  We'll just make one big String here to make it easy to check
74     // that the right values are being output.
75     private static final byte[] greaseBytes = new byte[] {
76         (byte) 0x0A, (byte) 0x1A, (byte) 0x2A, (byte) 0x3A,
77         (byte) 0x4A, (byte) 0x5A, (byte) 0x6A, (byte) 0x7A,
78         (byte) 0x8A, (byte) 0x9A, (byte) 0xAA, (byte) 0xBA,
79         (byte) 0xCA, (byte) 0xDA, (byte) 0xEA, (byte) 0xFA
80     };
81 
82     private static final String greaseString =
83             new String(greaseBytes, StandardCharsets.ISO_8859_1);
84 
findGreaseInClientHello(byte[] bytes)85     private static void findGreaseInClientHello(byte[] bytes) throws Exception {
86         for (int i = 0; i < bytes.length - greaseBytes.length; i++) {
87             if (Arrays.equals(bytes, i, i + greaseBytes.length,
88                     greaseBytes, 0, greaseBytes.length)) {
89                 System.out.println("Found greaseBytes in ClientHello at: " + i);
90                 return;
91             }
92         }
93         throw new Exception("Couldn't find greaseBytes");
94     }
95 
AlpnGreaseTest()96     private AlpnGreaseTest() throws Exception {
97         serverEngine = configureServerEngine(
98                 createServerSSLContext().createSSLEngine());
99 
100         clientEngine = configureClientEngine(
101                 createClientSSLContext().createSSLEngine());
102 
103         // We'll assume the buffer sizes are the same
104         // between client and server.
105         SSLSession session = clientEngine.getSession();
106         int appBufferMax = session.getApplicationBufferSize();
107         int netBufferMax = session.getPacketBufferSize();
108 
109         // We'll make the input buffers a bit bigger than the max needed
110         // size, so that unwrap()s following a successful data transfer
111         // won't generate BUFFER_OVERFLOWS.
112         //
113         // We'll use a mix of direct and indirect ByteBuffers for
114         // tutorial purposes only.  In reality, only use direct
115         // ByteBuffers when they give a clear performance enhancement.
116         clientIn = ByteBuffer.allocate(appBufferMax + 50);
117         serverIn = ByteBuffer.allocate(appBufferMax + 50);
118 
119         cTOs = ByteBuffer.allocateDirect(netBufferMax);
120         sTOc = ByteBuffer.allocateDirect(netBufferMax);
121 
122         clientOut = ByteBuffer.wrap("Hi Server, I'm Client".getBytes());
123         serverOut = ByteBuffer.wrap("Hello Client, I'm Server".getBytes());
124     }
125 
126     //
127     // Protected methods could be used to customize the test case.
128     //
129 
130     /*
131      * Configure the client side engine.
132      */
configureClientEngine(SSLEngine clientEngine)133     protected SSLEngine configureClientEngine(SSLEngine clientEngine) {
134         clientEngine.setUseClientMode(true);
135 
136         // Get/set parameters if needed
137         SSLParameters paramsClient = clientEngine.getSSLParameters();
138         paramsClient.setApplicationProtocols(new String[] { greaseString });
139 
140         clientEngine.setSSLParameters(paramsClient);
141 
142         return clientEngine;
143     }
144 
145     /*
146      * Configure the server side engine.
147      */
configureServerEngine(SSLEngine serverEngine)148     protected SSLEngine configureServerEngine(SSLEngine serverEngine) {
149         serverEngine.setUseClientMode(false);
150         serverEngine.setNeedClientAuth(true);
151 
152         // Get/set parameters if needed
153         //
154         SSLParameters paramsServer = serverEngine.getSSLParameters();
155         paramsServer.setApplicationProtocols(new String[] { greaseString });
156         serverEngine.setSSLParameters(paramsServer);
157 
158         return serverEngine;
159     }
160 
main(String[] args)161     public static void main(String[] args) throws Exception {
162         new AlpnGreaseTest().runTest();
163     }
164 
165     //
166     // Private methods that used to build the common part of the test.
167     //
168 
runTest()169     private void runTest() throws Exception {
170         SSLEngineResult clientResult;
171         SSLEngineResult serverResult;
172 
173         boolean dataDone = false;
174         boolean firstClientWrap = true;
175         while (isOpen(clientEngine) || isOpen(serverEngine)) {
176             log("=================");
177 
178             // client wrap
179             log("---Client Wrap---");
180             clientResult = clientEngine.wrap(clientOut, cTOs);
181             logEngineStatus(clientEngine, clientResult);
182             runDelegatedTasks(clientEngine);
183 
184             if (firstClientWrap) {
185                 firstClientWrap = false;
186                 byte[] bytes = new byte[cTOs.position()];
187                 cTOs.duplicate().flip().get(bytes);
188                 findGreaseInClientHello(bytes);
189             }
190 
191             // server wrap
192             log("---Server Wrap---");
193             serverResult = serverEngine.wrap(serverOut, sTOc);
194             logEngineStatus(serverEngine, serverResult);
195             runDelegatedTasks(serverEngine);
196 
197             cTOs.flip();
198             sTOc.flip();
199 
200             // client unwrap
201             log("---Client Unwrap---");
202             clientResult = clientEngine.unwrap(sTOc, clientIn);
203             logEngineStatus(clientEngine, clientResult);
204             runDelegatedTasks(clientEngine);
205 
206             // server unwrap
207             log("---Server Unwrap---");
208             serverResult = serverEngine.unwrap(cTOs, serverIn);
209             logEngineStatus(serverEngine, serverResult);
210             runDelegatedTasks(serverEngine);
211 
212             cTOs.compact();
213             sTOc.compact();
214 
215             // After we've transferred all application data between the client
216             // and server, we close the clientEngine's outbound stream.
217             // This generates a close_notify handshake message, which the
218             // server engine receives and responds by closing itself.
219             if (!dataDone && (clientOut.limit() == serverIn.position()) &&
220                     (serverOut.limit() == clientIn.position())) {
221 
222                 // Check ALPN Value
223                 String alpnServerValue = serverEngine.getApplicationProtocol();
224                 String alpnClientValue = clientEngine.getApplicationProtocol();
225 
226                 if (!alpnServerValue.equals(greaseString)
227                         || !alpnClientValue.equals(greaseString)) {
228                     throw new Exception("greaseString didn't match");
229                 }
230 
231                 // A sanity check to ensure we got what was sent.
232                 checkTransfer(serverOut, clientIn);
233                 checkTransfer(clientOut, serverIn);
234 
235                 log("\tClosing clientEngine's *OUTBOUND*...");
236                 clientEngine.closeOutbound();
237                 logEngineStatus(clientEngine);
238 
239                 dataDone = true;
240                 log("\tClosing serverEngine's *OUTBOUND*...");
241                 serverEngine.closeOutbound();
242                 logEngineStatus(serverEngine);
243             }
244         }
245     }
246 
isOpen(SSLEngine engine)247     private static boolean isOpen(SSLEngine engine) {
248         return (!engine.isOutboundDone() || !engine.isInboundDone());
249     }
250 
logEngineStatus(SSLEngine engine)251     private static void logEngineStatus(SSLEngine engine) {
252         log("\tCurrent HS State: " + engine.getHandshakeStatus());
253         log("\tisInboundDone() : " + engine.isInboundDone());
254         log("\tisOutboundDone(): " + engine.isOutboundDone());
255     }
256 
logEngineStatus( SSLEngine engine, SSLEngineResult result)257     private static void logEngineStatus(
258             SSLEngine engine, SSLEngineResult result) {
259         log("\tResult Status    : " + result.getStatus());
260         log("\tResult HS Status : " + result.getHandshakeStatus());
261         log("\tEngine HS Status : " + engine.getHandshakeStatus());
262         log("\tisInboundDone()  : " + engine.isInboundDone());
263         log("\tisOutboundDone() : " + engine.isOutboundDone());
264         log("\tMore Result      : " + result);
265     }
266 
log(String message)267     private static void log(String message) {
268         System.err.println(message);
269     }
270 
271     // If the result indicates that we have outstanding tasks to do,
272     // go ahead and run them in this thread.
runDelegatedTasks(SSLEngine engine)273     private static void runDelegatedTasks(SSLEngine engine) throws Exception {
274         if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
275             Runnable runnable;
276             while ((runnable = engine.getDelegatedTask()) != null) {
277                 log("    running delegated task...");
278                 runnable.run();
279             }
280             HandshakeStatus hsStatus = engine.getHandshakeStatus();
281             if (hsStatus == HandshakeStatus.NEED_TASK) {
282                 throw new Exception(
283                         "handshake shouldn't need additional tasks");
284             }
285             logEngineStatus(engine);
286         }
287     }
288 
289     // Simple check to make sure everything came across as expected.
checkTransfer(ByteBuffer a, ByteBuffer b)290     private static void checkTransfer(ByteBuffer a, ByteBuffer b)
291             throws Exception {
292         a.flip();
293         b.flip();
294 
295         if (!a.equals(b)) {
296             throw new Exception("Data didn't transfer cleanly");
297         } else {
298             log("\tData transferred cleanly");
299         }
300 
301         a.position(a.limit());
302         b.position(b.limit());
303         a.limit(a.capacity());
304         b.limit(b.capacity());
305     }
306 }
307