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 }