|
1 /* |
|
2 * Copyright (c) 2004, 2016, 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 * @test |
|
26 * @bug 8133632 |
|
27 * @summary javax.net.ssl.SSLEngine does not properly handle received |
|
28 * SSL fatal alerts |
|
29 * @run main/othervm EngineCloseOnAlert |
|
30 */ |
|
31 |
|
32 import java.io.FileInputStream; |
|
33 import java.io.IOException; |
|
34 import javax.net.ssl.*; |
|
35 import java.nio.ByteBuffer; |
|
36 import java.util.*; |
|
37 import java.security.*; |
|
38 import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*; |
|
39 |
|
40 public class EngineCloseOnAlert { |
|
41 |
|
42 private static final String pathToStores = "../etc"; |
|
43 private static final String keyStoreFile = "keystore"; |
|
44 private static final String trustStoreFile = "truststore"; |
|
45 private static final String passwd = "passphrase"; |
|
46 private static final String keyFilename = |
|
47 System.getProperty("test.src", ".") + "/" + pathToStores + |
|
48 "/" + keyStoreFile; |
|
49 private static final String trustFilename = |
|
50 System.getProperty("test.src", ".") + "/" + pathToStores + |
|
51 "/" + trustStoreFile; |
|
52 |
|
53 private static KeyManagerFactory KMF; |
|
54 private static TrustManagerFactory TMF; |
|
55 private static TrustManagerFactory EMPTY_TMF; |
|
56 |
|
57 private static final String[] TLS10ONLY = { "TLSv1" }; |
|
58 private static final String[] TLS12ONLY = { "TLSv1.2" }; |
|
59 private static final String[] ONECIPHER = |
|
60 { "TLS_RSA_WITH_AES_128_CBC_SHA" }; |
|
61 |
|
62 public interface TestCase { |
|
63 public void runTest() throws Exception; |
|
64 } |
|
65 |
|
66 public static void main(String[] args) throws Exception { |
|
67 int failed = 0; |
|
68 List<TestCase> testMatrix = new LinkedList<TestCase>() {{ |
|
69 add(clientReceivesAlert); |
|
70 add(serverReceivesAlert); |
|
71 }}; |
|
72 |
|
73 // Create the various key/trust manager factories we'll need |
|
74 createManagerFactories(); |
|
75 |
|
76 for (TestCase test : testMatrix) { |
|
77 try { |
|
78 test.runTest(); |
|
79 } catch (Exception e) { |
|
80 System.out.println("Exception in test:\n" + e); |
|
81 e.printStackTrace(System.out); |
|
82 failed++; |
|
83 } |
|
84 } |
|
85 |
|
86 System.out.println("Total tests: " + testMatrix.size() + ", passed: " + |
|
87 (testMatrix.size() - failed) + ", failed: " + failed); |
|
88 if (failed > 0) { |
|
89 throw new RuntimeException("One or more tests failed."); |
|
90 } |
|
91 } |
|
92 |
|
93 private static final TestCase clientReceivesAlert = new TestCase() { |
|
94 @Override |
|
95 public void runTest() throws Exception { |
|
96 System.out.println(""); |
|
97 System.out.println("======================================="); |
|
98 System.out.println("Test: Client receives alert from server"); |
|
99 System.out.println("======================================="); |
|
100 |
|
101 // For this test, we won't initialize any keystore so the |
|
102 // server will throw an exception because it has no key/cert to |
|
103 // match the requested ciphers offered by the client. This |
|
104 // will generate an alert from the server to the client. |
|
105 |
|
106 SSLContext context = SSLContext.getDefault(); |
|
107 SSLEngine client = context.createSSLEngine(); |
|
108 SSLEngine server = context.createSSLEngine(); |
|
109 client.setUseClientMode(true); |
|
110 server.setUseClientMode(false); |
|
111 SSLEngineResult clientResult; |
|
112 SSLEngineResult serverResult; |
|
113 |
|
114 ByteBuffer raw = ByteBuffer.allocate(32768); |
|
115 ByteBuffer plain = ByteBuffer.allocate(32768); |
|
116 |
|
117 // Generate the client hello and have the server unwrap it |
|
118 client.wrap(plain, raw); |
|
119 checkEngineState(client, NEED_UNWRAP, false, false); |
|
120 raw.flip(); |
|
121 System.out.println("Client-to-Server:\n-----------------\n" + |
|
122 dumpHexBytes(raw, 16, "\n", ":")); |
|
123 |
|
124 |
|
125 // The server should need to run a delegated task while processing |
|
126 // the client hello data. |
|
127 serverResult = server.unwrap(raw, plain); |
|
128 checkEngineState(server, NEED_TASK, false, false); |
|
129 System.out.println("Server result: " + serverResult); |
|
130 runDelegatedTasks(serverResult, server); |
|
131 checkEngineState(server, NEED_WRAP, true, false); |
|
132 |
|
133 try { |
|
134 raw.clear(); |
|
135 serverResult = server.wrap(plain, raw); |
|
136 System.out.println("Server result: " + serverResult); |
|
137 runDelegatedTasks(serverResult, server); |
|
138 } catch (SSLException e) { |
|
139 // This is the expected code path |
|
140 System.out.println("Server throws exception: " + e); |
|
141 System.out.println("Server engine state: " + |
|
142 "isInboundDone = "+ server.isInboundDone() + |
|
143 ", isOutboundDone = " + server.isOutboundDone() + |
|
144 ", handshake status = " + server.getHandshakeStatus()); |
|
145 checkEngineState(server, NEED_WRAP, true, false); |
|
146 } |
|
147 raw.clear(); |
|
148 |
|
149 // The above should show that isInboundDone returns true, and |
|
150 // handshake status is NEED_WRAP. That is the correct behavior, |
|
151 // wrap will put a fatal alert message in the buffer. |
|
152 serverResult = server.wrap(plain, raw); |
|
153 System.out.println("Server result (wrap after exception): " + |
|
154 serverResult); |
|
155 System.out.println("Server engine closure state: isInboundDone=" |
|
156 + server.isInboundDone() + ", isOutboundDone=" |
|
157 + server.isOutboundDone()); |
|
158 checkEngineState(server, NEED_UNWRAP, true, true); |
|
159 raw.flip(); |
|
160 |
|
161 System.out.println("Server-to-Client:\n-----------------\n" + |
|
162 dumpHexBytes(raw, 16, "\n", ":")); |
|
163 |
|
164 // Client side will read the fatal alert and throw exception. |
|
165 try { |
|
166 clientResult = client.unwrap(raw, plain); |
|
167 System.out.println("Client result (unwrap alert): " + |
|
168 clientResult); |
|
169 } catch (SSLException e) { |
|
170 System.out.println("Client throws exception: " + e); |
|
171 System.out.println("Engine closure status: isInboundDone=" |
|
172 + client.isInboundDone() + ", isOutboundDone=" |
|
173 + client.isOutboundDone() + ", handshake status=" |
|
174 + client.getHandshakeStatus()); |
|
175 checkEngineState(client, NOT_HANDSHAKING, true, true); |
|
176 } |
|
177 raw.clear(); |
|
178 |
|
179 // Last test, we try to unwrap |
|
180 clientResult = client.unwrap(raw, plain); |
|
181 checkEngineState(client, NOT_HANDSHAKING, true, true); |
|
182 System.out.println("Client result (wrap after exception): " + |
|
183 clientResult); |
|
184 } |
|
185 }; |
|
186 |
|
187 private static final TestCase serverReceivesAlert = new TestCase() { |
|
188 @Override |
|
189 public void runTest() throws Exception { |
|
190 SSLContext cliContext = SSLContext.getDefault(); |
|
191 SSLContext servContext = SSLContext.getInstance("TLS"); |
|
192 servContext.init(KMF.getKeyManagers(), TMF.getTrustManagers(), |
|
193 null); |
|
194 SSLEngine client = cliContext.createSSLEngine(); |
|
195 SSLEngine server = servContext.createSSLEngine(); |
|
196 client.setUseClientMode(true); |
|
197 client.setEnabledProtocols(TLS12ONLY); |
|
198 client.setEnabledCipherSuites(ONECIPHER); |
|
199 server.setUseClientMode(false); |
|
200 server.setEnabledProtocols(TLS10ONLY); |
|
201 SSLEngineResult clientResult; |
|
202 SSLEngineResult serverResult; |
|
203 ByteBuffer raw = ByteBuffer.allocate(32768); |
|
204 ByteBuffer plain = ByteBuffer.allocate(32768); |
|
205 |
|
206 System.out.println(""); |
|
207 System.out.println("======================================="); |
|
208 System.out.println("Test: Server receives alert from client"); |
|
209 System.out.println("======================================="); |
|
210 |
|
211 // Generate the client hello and have the server unwrap it |
|
212 checkEngineState(client, NOT_HANDSHAKING, false, false); |
|
213 client.wrap(plain, raw); |
|
214 checkEngineState(client, NEED_UNWRAP, false, false); |
|
215 raw.flip(); |
|
216 System.out.println("Client-to-Server:\n-----------------\n" + |
|
217 dumpHexBytes(raw, 16, "\n", ":")); |
|
218 |
|
219 // The server should need to run a delegated task while processing |
|
220 // the client hello data. |
|
221 serverResult = server.unwrap(raw, plain); |
|
222 checkEngineState(server, NEED_TASK, false, false); |
|
223 runDelegatedTasks(serverResult, server); |
|
224 checkEngineState(server, NEED_WRAP, false, false); |
|
225 raw.compact(); |
|
226 |
|
227 // The server should now wrap the response back to the client |
|
228 server.wrap(plain, raw); |
|
229 checkEngineState(server, NEED_UNWRAP, false, false); |
|
230 raw.flip(); |
|
231 System.out.println("Server-to-Client:\n-----------------\n" + |
|
232 dumpHexBytes(raw, 16, "\n", ":")); |
|
233 |
|
234 // The client should parse this and throw an exception because |
|
235 // It is unwiling to do TLS 1.0 |
|
236 clientResult = client.unwrap(raw, plain); |
|
237 checkEngineState(client, NEED_TASK, false, false); |
|
238 runDelegatedTasks(clientResult, client); |
|
239 checkEngineState(client, NEED_UNWRAP, false, false); |
|
240 |
|
241 try { |
|
242 client.unwrap(raw, plain); |
|
243 } catch (SSLException e) { |
|
244 System.out.println("Client throws exception: " + e); |
|
245 System.out.println("Engine closure status: isInboundDone=" |
|
246 + client.isInboundDone() + ", isOutboundDone=" |
|
247 + client.isOutboundDone() + ", handshake status=" |
|
248 + client.getHandshakeStatus()); |
|
249 checkEngineState(client, NEED_WRAP, true, false); |
|
250 } |
|
251 raw.clear(); |
|
252 |
|
253 // Now the client should wrap the exception |
|
254 client.wrap(plain, raw); |
|
255 checkEngineState(client, NEED_UNWRAP, true, true); |
|
256 raw.flip(); |
|
257 System.out.println("Client-to-Server:\n-----------------\n" + |
|
258 dumpHexBytes(raw, 16, "\n", ":")); |
|
259 |
|
260 try { |
|
261 server.unwrap(raw, plain); |
|
262 checkEngineState(server, NEED_UNWRAP, false, false); |
|
263 } catch (SSLException e) { |
|
264 System.out.println("Server throws exception: " + e); |
|
265 System.out.println("Engine closure status: isInboundDone=" |
|
266 + server.isInboundDone() + ", isOutboundDone=" |
|
267 + server.isOutboundDone() + ", handshake status=" |
|
268 + server.getHandshakeStatus()); |
|
269 checkEngineState(server, NOT_HANDSHAKING, true, true); |
|
270 } |
|
271 raw.clear(); |
|
272 } |
|
273 }; |
|
274 |
|
275 |
|
276 /* |
|
277 * If the result indicates that we have outstanding tasks to do, |
|
278 * go ahead and run them in this thread. |
|
279 */ |
|
280 private static void runDelegatedTasks(SSLEngineResult result, |
|
281 SSLEngine engine) throws Exception { |
|
282 |
|
283 if (result.getHandshakeStatus() == |
|
284 SSLEngineResult.HandshakeStatus.NEED_TASK) { |
|
285 Runnable runnable; |
|
286 while ((runnable = engine.getDelegatedTask()) != null) { |
|
287 System.out.println("\trunning delegated task..."); |
|
288 runnable.run(); |
|
289 } |
|
290 SSLEngineResult.HandshakeStatus hsStatus = |
|
291 engine.getHandshakeStatus(); |
|
292 if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) { |
|
293 throw new Exception( |
|
294 "handshake shouldn't need additional tasks"); |
|
295 } |
|
296 System.out.println("\tnew HandshakeStatus: " + hsStatus); |
|
297 } |
|
298 } |
|
299 |
|
300 /** |
|
301 * |
|
302 * @param data The array of bytes to dump to stdout. |
|
303 * @param itemsPerLine The number of bytes to display per line |
|
304 * if the {@code lineDelim} character is blank then all bytes will be |
|
305 * printed on a single line. |
|
306 * @param lineDelim The delimiter between lines |
|
307 * @param itemDelim The delimiter between bytes |
|
308 * |
|
309 * @return The hexdump of the byte array |
|
310 */ |
|
311 private static String dumpHexBytes(ByteBuffer data, int itemsPerLine, |
|
312 String lineDelim, String itemDelim) { |
|
313 StringBuilder sb = new StringBuilder(); |
|
314 |
|
315 if (data != null) { |
|
316 data.mark(); |
|
317 for (int i = 0; i < data.limit(); i++) { |
|
318 if (i % itemsPerLine == 0 && i != 0) { |
|
319 sb.append(lineDelim); |
|
320 } |
|
321 sb.append(String.format("%02X", data.get(i))); |
|
322 if (i % itemsPerLine != (itemsPerLine - 1) && |
|
323 i != (data.limit() -1)) { |
|
324 sb.append(itemDelim); |
|
325 } |
|
326 } |
|
327 data.reset(); |
|
328 } |
|
329 |
|
330 return sb.toString(); |
|
331 } |
|
332 |
|
333 private static void createManagerFactories() |
|
334 throws GeneralSecurityException, IOException { |
|
335 KeyStore keystore = KeyStore.getInstance("PKCS12"); |
|
336 KeyStore truststore = KeyStore.getInstance("PKCS12"); |
|
337 KeyStore empty_ts = KeyStore.getInstance("PKCS12"); |
|
338 char[] passphrase = passwd.toCharArray(); |
|
339 |
|
340 keystore.load(new FileInputStream(keyFilename), passphrase); |
|
341 truststore.load(new FileInputStream(trustFilename), passphrase); |
|
342 empty_ts.load(null, "".toCharArray()); |
|
343 |
|
344 KMF = KeyManagerFactory.getInstance("PKIX"); |
|
345 KMF.init(keystore, passphrase); |
|
346 TMF = TrustManagerFactory.getInstance("PKIX"); |
|
347 TMF.init(truststore); |
|
348 EMPTY_TMF = TrustManagerFactory.getInstance("PKIX"); |
|
349 EMPTY_TMF.init(truststore); |
|
350 } |
|
351 |
|
352 private static void checkEngineState(SSLEngine engine, |
|
353 SSLEngineResult.HandshakeStatus expectedHSStat, |
|
354 boolean expectedInboundDone, boolean expectedOutboundDone) { |
|
355 if (engine.getHandshakeStatus() != expectedHSStat || |
|
356 engine.isInboundDone() != expectedInboundDone || |
|
357 engine.isOutboundDone() != expectedOutboundDone) { |
|
358 throw new RuntimeException("Error: engine not in expected state\n" + |
|
359 "Expected: state = " + expectedHSStat + |
|
360 ", inDone = " + expectedInboundDone + |
|
361 ", outDone = " + expectedOutboundDone + "\n" + |
|
362 "Actual: state = " + engine.getHandshakeStatus() + |
|
363 ", inDone = " + engine.isInboundDone() + |
|
364 ", outDone = " + engine.isOutboundDone()); |
|
365 } else { |
|
366 System.out.println((engine.getUseClientMode() ? |
|
367 "Client" : "Server") + " handshake status: " + |
|
368 engine.getHandshakeStatus() + ", inDone = " + |
|
369 engine.isInboundDone() + ", outDone = " + |
|
370 engine.isOutboundDone()); |
|
371 } |
|
372 } |
|
373 } |