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