diff --git a/org/mozilla/jss/tests/SSLEngineSimpleDemo.java b/org/mozilla/jss/tests/SSLEngineSimpleDemo.java new file mode 100644 index 000000000..4ad79d8c0 --- /dev/null +++ b/org/mozilla/jss/tests/SSLEngineSimpleDemo.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * -Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Oracle nor the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN + * OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR + * FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + */ + +/** + * A SSLEngine usage example which simplifies the presentation + * by removing the I/O and multi-threading concerns. + * + * The demo creates two SSLEngines, simulating a client and server. + * The "transport" layer consists two ByteBuffers: think of them + * as directly connected pipes. + * + * Note, this is a *very* simple example: real code will be much more + * involved. For example, different threading and I/O models could be + * used, transport mechanisms could close unexpectedly, and so on. + * + * When this application runs, notice that several messages + * (wrap/unwrap) pass before any application data is consumed or + * produced. (For more information, please see the SSL/TLS + * specifications.) There may several steps for a successful handshake, + * so it's typical to see the following series of operations: + * + * client server message + * ====== ====== ======= + * wrap() ... ClientHello + * ... unwrap() ClientHello + * ... wrap() ServerHello/Certificate + * unwrap() ... ServerHello/Certificate + * wrap() ... ClientKeyExchange + * wrap() ... ChangeCipherSpec + * wrap() ... Finished + * ... unwrap() ClientKeyExchange + * ... unwrap() ChangeCipherSpec + * ... unwrap() Finished + * ... wrap() ChangeCipherSpec + * ... wrap() Finished + * unwrap() ... ChangeCipherSpec + * unwrap() ... Finished + */ + +package org.mozilla.jss.tests; + +import javax.net.ssl.*; +import javax.net.ssl.SSLEngineResult.*; +import java.io.*; +import java.security.*; +import java.nio.*; +import java.nio.channels.*; +import java.net.*; + +import org.mozilla.jss.*; +import org.mozilla.jss.provider.javax.crypto.*; +import org.mozilla.jss.ssl.*; +import org.mozilla.jss.ssl.javax.*; + +public class SSLEngineSimpleDemo { + + /* + * Enables logging of the SSLEngine operations. + */ + private static boolean logging = true; + + /* + * Enables the JSSE system debugging system property: + * + * -Djavax.net.debug=all + * + * This gives a lot of low-level information about operations underway, + * including specific handshake messages, and might be best examined + * after gaining some familiarity with this application. + */ + private static boolean debug = true; + + private SSLContext sslc; + + private SSLEngine clientEngine; // client Engine + private ByteBuffer clientOut; // write side of clientEngine + private ByteBuffer clientIn; // read side of clientEngine + + private SSLEngine serverEngine; // server Engine + private ByteBuffer serverOut; // write side of serverEngine + private ByteBuffer serverIn; // read side of serverEngine + + private String alias = "sslserver"; + + /* + * For data transport, this example uses local ByteBuffers. This + * isn't really useful, but the purpose of this example is to show + * SSLEngine concepts, not how to do network transport. + */ + private ByteBuffer cTOs; // "reliable" transport client->server + private ByteBuffer sTOc; // "reliable" transport server->client + + /* + * Main entry point for this demo. + */ + public static void main(String args[]) throws Exception { + if (debug) { + System.setProperty("javax.net.debug", "all"); + } + + CryptoManager cm; + try { + cm = CryptoManager.getInstance(); + } catch (Exception e) { + CryptoManager.initialize(args[0]); + cm = CryptoManager.getInstance(); + } + cm.setPasswordCallback(new FilePasswordCallback(args[1])); + + SSLEngineSimpleDemo demo = new SSLEngineSimpleDemo(); + if (args.length >= 3) { + demo.alias = args[2]; + } + demo.runDemo(); + + System.out.println("Demo Completed."); + } + + /* + * Create an initialized SSLContext to use for this demo. + */ + public SSLEngineSimpleDemo() throws Exception { + KeyManagerFactory kmf = KeyManagerFactory.getInstance("NssX509", "Mozilla-JSS"); + + SSLContext sslCtx = SSLContext.getInstance("TLS", "Mozilla-JSS"); + sslCtx.init(kmf.getKeyManagers(), new X509TrustManager[] { new JSSNativeTrustManager() }, null); + + sslc = sslCtx; + } + + /* + * Run the demo. + * + * Sit in a tight loop, both engines calling wrap/unwrap regardless + * of whether data is available or not. We do this until both engines + * report back they are closed. + * + * The main loop handles all of the I/O phases of the SSLEngine's + * lifetime: + * + * initial handshaking + * application data transfer + * engine closing + * + * One could easily separate these phases into separate + * sections of code. + */ + private void runDemo() throws Exception { + boolean dataDone = false; + + createSSLEngines(); + createBuffers(); + + SSLEngineResult clientResult; // results from client's last operation + SSLEngineResult serverResult; // results from server's last operation + + /* + * Examining the SSLEngineResults could be much more involved, + * and may alter the overall flow of the application. + * + * For example, if we received a BUFFER_OVERFLOW when trying + * to write to the output pipe, we could reallocate a larger + * pipe, but instead we wait for the peer to drain it. + */ + int counter = 0; + int max_counter = 40; + + + while (!isEngineClosed(clientEngine) || + !isEngineClosed(serverEngine)) { + + log("================"); + System.out.println("clientOut=" + clientOut); + System.out.println("serverOut=" + serverOut); + System.out.println("clientIn=" + clientIn); + System.out.println("serverIn=" + serverIn); + + + clientResult = clientEngine.wrap(clientOut, cTOs); + log("client wrap: ", clientResult); + runDelegatedTasks(clientResult, clientEngine); + + serverResult = serverEngine.wrap(serverOut, sTOc); + log("server wrap: ", serverResult); + runDelegatedTasks(serverResult, serverEngine); + + cTOs.flip(); + sTOc.flip(); + + log("----"); + + clientResult = clientEngine.unwrap(sTOc, clientIn); + log("client unwrap: ", clientResult); + runDelegatedTasks(clientResult, clientEngine); + + serverResult = serverEngine.unwrap(cTOs, serverIn); + log("server unwrap: ", serverResult); + runDelegatedTasks(serverResult, serverEngine); + + cTOs.compact(); + sTOc.compact(); + + /* + * After we've transfered all application data between the client + * and server, we close the clientEngine's outbound stream. + * This generates a close_notify handshake message, which the + * server engine receives and responds by closing itself. + * + * In normal operation, each SSLEngine should call + * closeOutbound(). To protect against truncation attacks, + * SSLEngine.closeInbound() should be called whenever it has + * determined that no more input data will ever be + * available (say a closed input stream). + */ + if (!dataDone && (clientOut.limit() == serverIn.position()) && + (serverOut.limit() == clientIn.position())) { + + /* + * A sanity check to ensure we got what was sent. + */ + checkTransfer(serverOut, clientIn); + checkTransfer(clientOut, serverIn); + + log("\tClosing clientEngine's *OUTBOUND*..."); + clientEngine.closeOutbound(); + // serverEngine.closeOutbound(); + dataDone = true; + } + + /* + if (clientOut.remaining() == 0) { + log("\tClosing clientEngine's *OUTBOUND*..."); + clientEngine.closeOutbound(); + } + + if (serverOut.remaining() == 0) { + log("\tClosing serverEngine's *OUTBOUND*..."); + serverEngine.closeOutbound(); + }*/ + + counter += 1; + if (counter == max_counter) { + throw new RuntimeException("Reasonably expected to be done after " + counter + " steps, but still working..."); + } + } + + checkTransfer(serverOut, clientIn); + checkTransfer(clientOut, serverIn); + } + + /* + * Using the SSLContext created during object creation, + * create/configure the SSLEngines we'll use for this demo. + */ + private void createSSLEngines() throws Exception { + /* + * Configure the serverEngine to act as a server in the SSL/TLS + * handshake. Also, require SSL client authentication. + */ + serverEngine = sslc.createSSLEngine(); + serverEngine.setUseClientMode(false); + serverEngine.setNeedClientAuth(false); + serverEngine.setWantClientAuth(false); + + JSSParameters server_params = new JSSParameters(); + server_params.setAlias(alias); + server_params.setProtocols(SSLVersion.TLS_1_2, SSLVersion.TLS_1_2); + serverEngine.setSSLParameters(server_params); + + /* + * Similar to above, but using client mode instead. + */ + clientEngine = sslc.createSSLEngine("client", 80); + clientEngine.setUseClientMode(true); + + JSSParameters client_params = new JSSParameters(); + client_params.setHostname("localhost"); + client_params.setProtocols(SSLVersion.TLS_1_2, SSLVersion.TLS_1_2); + clientEngine.setSSLParameters(client_params); + } + + /* + * Create and size the buffers appropriately. + */ + private void createBuffers() { + + /* + * We'll assume the buffer sizes are the same + * between client and server. + */ + SSLSession session = clientEngine.getSession(); + int appBufferMax = session.getApplicationBufferSize(); + int netBufferMax = session.getPacketBufferSize(); + + /* + * We'll make the input buffers a bit bigger than the max needed + * size, so that unwrap()s following a successful data transfer + * won't generate BUFFER_OVERFLOWS. + * + * We'll use a mix of direct and indirect ByteBuffers for + * tutorial purposes only. In reality, only use direct + * ByteBuffers when they give a clear performance enhancement. + */ + clientIn = ByteBuffer.allocate(appBufferMax + 50); + serverIn = ByteBuffer.allocate(appBufferMax + 50); + + cTOs = ByteBuffer.allocate(netBufferMax); + sTOc = ByteBuffer.allocate(netBufferMax); + + clientOut = ByteBuffer.wrap("Hi Server, I'm Client".getBytes()); + serverOut = ByteBuffer.wrap("Hello Client, I'm Server".getBytes()); + } + + /* + * If the result indicates that we have outstanding tasks to do, + * go ahead and run them in this thread. + */ + private static void runDelegatedTasks(SSLEngineResult result, + SSLEngine engine) throws Exception { + + if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + Runnable runnable; + while ((runnable = engine.getDelegatedTask()) != null) { + log("\trunning delegated task..."); + runnable.run(); + } + HandshakeStatus hsStatus = engine.getHandshakeStatus(); + if (hsStatus == HandshakeStatus.NEED_TASK) { + throw new Exception( + "handshake shouldn't need additional tasks"); + } + log("\tnew HandshakeStatus: " + hsStatus); + } + } + + private static boolean isEngineClosed(SSLEngine engine) { + return (engine.isOutboundDone() && engine.isInboundDone()); + } + + /* + * Simple check to make sure everything came across as expected. + */ + private static void checkTransfer(ByteBuffer a, ByteBuffer b) + throws Exception { + a.flip(); + b.flip(); + + if (!a.equals(b)) { + throw new Exception("Data didn't transfer cleanly"); + } else { + log("\tData transferred cleanly"); + } + + a.position(a.limit()); + b.position(b.limit()); + a.limit(a.capacity()); + b.limit(b.capacity()); + } + + /* + * Logging code + */ + private static boolean resultOnce = true; + + private static void log(String str, SSLEngineResult result) { + if (!logging) { + return; + } + if (resultOnce) { + resultOnce = false; + System.out.println("The format of the SSLEngineResult is: \n" + + "\t\"getStatus() / getHandshakeStatus()\" +\n" + + "\t\"bytesConsumed() / bytesProduced()\"\n"); + } + HandshakeStatus hsStatus = result.getHandshakeStatus(); + log(str + + result.getStatus() + "/" + hsStatus + ", " + + result.bytesConsumed() + "/" + result.bytesProduced() + + " bytes"); + if (hsStatus == HandshakeStatus.FINISHED) { + log("\t...ready for application data"); + } + } + + private static void log(String str) { + if (logging) { + System.out.println(str); + } + } +}