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 }