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 }