You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by je...@apache.org on 2013/06/05 22:40:39 UTC

[3/3] git commit: Merge branch 'ssl-experiment' into trunk

Merge branch 'ssl-experiment' into trunk


Project: http://git-wip-us.apache.org/repos/asf/mina/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina/commit/31767ad4
Tree: http://git-wip-us.apache.org/repos/asf/mina/tree/31767ad4
Diff: http://git-wip-us.apache.org/repos/asf/mina/diff/31767ad4

Branch: refs/heads/trunk
Commit: 31767ad4ce30e71c981865598c9f8764b7e5d3f2
Parents: dbbdf9e 2859673
Author: Jeff MAURY <je...@apache.org>
Authored: Wed Jun 5 22:39:49 2013 +0200
Committer: Jeff MAURY <je...@apache.org>
Committed: Wed Jun 5 22:39:49 2013 +0200

----------------------------------------------------------------------
 .../org/apache/mina/transport/nio/SslHelper.java   |  300 +++++----------
 .../org/apache/mina/transport/nio/SslTest.java     |    5 +-
 .../org/apache/mina/transport/nio/keystore.cert    |  Bin 0 -> 937 bytes
 .../org/apache/mina/transport/nio/keystore.sslTest |  Bin 0 -> 1368 bytes
 .../apache/mina/transport/nio/truststore.sslTest   |  Bin 0 -> 654 bytes
 .../org/apache/mina/transport/tcp/keystore.cert    |  Bin 937 -> 0 bytes
 .../org/apache/mina/transport/tcp/keystore.sslTest |  Bin 1368 -> 0 bytes
 .../apache/mina/transport/tcp/truststore.sslTest   |  Bin 654 -> 0 bytes
 8 files changed, 103 insertions(+), 202 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina/blob/31767ad4/core/src/main/java/org/apache/mina/transport/nio/SslHelper.java
----------------------------------------------------------------------
diff --cc core/src/main/java/org/apache/mina/transport/nio/SslHelper.java
index d6e063f,0000000..c2996d2
mode 100644,000000..100644
--- a/core/src/main/java/org/apache/mina/transport/nio/SslHelper.java
+++ b/core/src/main/java/org/apache/mina/transport/nio/SslHelper.java
@@@ -1,421 -1,0 +1,323 @@@
 +/*
 + *  Licensed to the Apache Software Foundation (ASF) under one
 + *  or more contributor license agreements.  See the NOTICE file
 + *  distributed with this work for additional information
 + *  regarding copyright ownership.  The ASF licenses this file
 + *  to you under the Apache License, Version 2.0 (the
 + *  "License"); you may not use this file except in compliance
 + *  with the License.  You may obtain a copy of the License at
 + *
 + *    http://www.apache.org/licenses/LICENSE-2.0
 + *
 + *  Unless required by applicable law or agreed to in writing,
 + *  software distributed under the License is distributed on an
 + *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + *  KIND, either express or implied.  See the License for the
 + *  specific language governing permissions and limitations
 + *  under the License.
 + *
 + */
 +package org.apache.mina.transport.nio;
 +
 +import static org.apache.mina.session.AttributeKey.createKey;
 +
 +import java.net.InetSocketAddress;
 +import java.nio.ByteBuffer;
 +import java.util.Queue;
 +
 +import javax.net.ssl.SSLContext;
 +import javax.net.ssl.SSLEngine;
 +import javax.net.ssl.SSLEngineResult;
 +import javax.net.ssl.SSLEngineResult.HandshakeStatus;
- import javax.net.ssl.SSLEngineResult.Status;
 +import javax.net.ssl.SSLException;
 +import javax.net.ssl.SSLSession;
 +
 +import org.apache.mina.api.IoClient;
 +import org.apache.mina.api.IoSession;
 +import org.apache.mina.api.IoSession.SessionState;
 +import org.apache.mina.session.AbstractIoSession;
 +import org.apache.mina.session.AttributeKey;
 +import org.apache.mina.session.DefaultWriteRequest;
 +import org.apache.mina.session.WriteRequest;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +
 +/**
 + * An helper class used to manage everything related to SSL/TLS establishment and management.
 + * 
 + * @author <a href="http://mina.apache.org">Apache MINA Project</a>
 + */
 +public class SslHelper {
 +    /** A logger for this class */
 +    private static final Logger LOGGER = LoggerFactory.getLogger(SslHelper.class);
 +
 +    /** The SSL engine instance */
 +    private SSLEngine sslEngine;
 +
 +    /** The SSLContext instance */
 +    private final SSLContext sslContext;
 +
 +    /** The current session */
 +    private final IoSession session;
 +
 +    /**
 +     * A session attribute key that should be set to an {@link InetSocketAddress}. Setting this attribute causes
 +     * {@link SSLContext#createSSLEngine(String, int)} to be called passing the hostname and port of the
 +     * {@link InetSocketAddress} to get an {@link SSLEngine} instance. If not set {@link SSLContext#createSSLEngine()}
 +     * will be called.<br/>
 +     * Using this feature {@link SSLSession} objects may be cached and reused when in client mode.
 +     * 
 +     * @see SSLContext#createSSLEngine(String, int)
 +     */
 +    public static final AttributeKey<InetSocketAddress> PEER_ADDRESS = createKey(InetSocketAddress.class,
 +            "internal_peerAddress");
 +
 +    public static final AttributeKey<Boolean> WANT_CLIENT_AUTH = createKey(Boolean.class, "internal_wantClientAuth");
 +
 +    public static final AttributeKey<Boolean> NEED_CLIENT_AUTH = createKey(Boolean.class, "internal_needClientAuth");
 +
 +    /** Incoming buffer accumulating bytes read from the channel */
 +    /** An empty buffer used during the handshake phase */
 +    private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
 +
 +    /** An empty buffer used during the handshake phase */
 +    private static final ByteBuffer HANDSHAKE_BUFFER = ByteBuffer.allocate(1024);
++    
++    private ByteBuffer previous = null;
 +
 +    /**
 +     * Create a new SSL Handler.
 +     * 
 +     * @param session The associated session
 +     */
 +    public SslHelper(IoSession session, SSLContext sslContext) {
 +        this.session = session;
 +        this.sslContext = sslContext;
 +    }
 +
 +    /**
 +     * @return The associated session
 +     */
 +    /* no qualifier */IoSession getSession() {
 +        return session;
 +    }
 +
 +    /**
 +     * @return The associated SSLEngine
 +     */
 +    /* no qualifier */SSLEngine getEngine() {
 +        return sslEngine;
 +    }
 +
++    boolean isHanshaking() {
++        return sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING;
++    }
++    
 +    /**
 +     * Initialize the SSL handshake.
 +     * 
 +     * @throws SSLException If the underlying SSLEngine handshake initialization failed
 +     */
 +    public void init() throws SSLException {
 +        if (sslEngine != null) {
 +            // We already have a SSL engine created, no need to create a new one
 +            return;
 +        }
 +
 +        LOGGER.debug("{} Initializing the SSL Helper", session);
 +
 +        InetSocketAddress peer = session.getAttribute(PEER_ADDRESS, null);
 +
 +        // Create the SSL engine here
 +        if (peer == null) {
 +            sslEngine = sslContext.createSSLEngine();
 +        } else {
 +            sslEngine = sslContext.createSSLEngine(peer.getHostName(), peer.getPort());
 +        }
 +
 +        // Initialize the engine in client mode if necessary
 +        sslEngine.setUseClientMode(session.getService() instanceof IoClient);
 +
 +        // Initialize the different SslEngine modes
 +        if (!sslEngine.getUseClientMode()) {
 +            // Those parameters are only valid when in server mode
 +            boolean needClientAuth = session.getAttribute(NEED_CLIENT_AUTH, false);
 +            boolean wantClientAuth = session.getAttribute(WANT_CLIENT_AUTH, false);
 +
 +            // The WantClientAuth supersede the NeedClientAuth, if set.
 +            if (needClientAuth) {
 +                sslEngine.setNeedClientAuth(true);
 +            }
 +
 +            if (wantClientAuth) {
 +                sslEngine.setWantClientAuth(true);
 +            }
 +        }
 +
 +        if (LOGGER.isDebugEnabled()) {
 +            LOGGER.debug("{} SSL Handler Initialization done.", session);
 +        }
 +    }
 +
 +    /**
-      * Process the NEED_TASK action.
++     * Duplicate a byte buffer for storing it into this context for future
++     * use.
 +     * 
-      * @param engine The SSLEngine instance
-      * @return The resulting HandshakeStatus
-      * @throws SSLException If we've got an error while processing the tasks
++     * @param buffer the buffer to duplicate
++     * @return the newly allocated buffer
 +     */
-     private HandshakeStatus processTasks(SSLEngine engine) throws SSLException {
-         Runnable runnable;
- 
-         while ((runnable = engine.getDelegatedTask()) != null) {
-             // TODO : we may have to use a thread pool here to improve the
-             // performances
-             runnable.run();
-         }
- 
-         HandshakeStatus hsStatus = engine.getHandshakeStatus();
- 
-         return hsStatus;
++    private ByteBuffer duplicate(ByteBuffer buffer) {
++        ByteBuffer newBuffer = ByteBuffer.allocateDirect(buffer.remaining() * 2);
++        newBuffer.put(buffer);
++        newBuffer.flip();
++        return newBuffer;
 +    }
- 
++    
 +    /**
-      * Process the NEED_UNWRAP action. We have to read the incoming buffer, and to feed the application buffer.
++     * Accumulate the given buffer into the current context. Allocation is performed only
++     * if needed.
++     * 
++     * @param buffer the buffer to accumulate
++     * @return the accumulated buffer
 +     */
-     private SSLEngineResult unwrap(ByteBuffer inBuffer, ByteBuffer appBuffer) throws SSLException {
-         // First work with either the new incoming buffer, or the accumulating buffer
-         ByteBuffer tempBuffer = inBuffer;
- 
-         // Loop until we have processed the entire incoming buffer,
-         // or until we have to stop
-         while (true) {
-             // Do the unwrapping
-             SSLEngineResult result = sslEngine.unwrap(tempBuffer, appBuffer);
- 
-             switch (result.getStatus()) {
-             case OK:
-                 // Ok, we have unwrapped a message, return.
-                 return result;
- 
-             case BUFFER_UNDERFLOW:
-                 // We need to read some more data from the channel.
- 
-                 inBuffer.clear();
- 
-                 return result;
- 
-             case CLOSED:
-                 // We have received a Close message, we can exit now
-                 if (session.isConnectedSecured()) {
-                     return result;
-                 } else {
-                     throw new IllegalStateException();
-                 }
- 
-             case BUFFER_OVERFLOW:
-                 // We have to increase the appBuffer size. In any case
-                 // we aren't processing an handshake here. Read again.
-                 appBuffer = ByteBuffer.allocate(appBuffer.capacity() + 4096);
-             }
++    private ByteBuffer accumulate(ByteBuffer buffer) {
++        if (previous.capacity() - previous.remaining() > buffer.remaining()) {
++            int oldPosition = previous.position();
++            previous.position(previous.limit());
++            previous.limit(previous.limit() + buffer.remaining());
++            previous.put(buffer);
++            previous.position(oldPosition);
++        } else {
++            ByteBuffer newPrevious = ByteBuffer.allocateDirect((previous.remaining() + buffer.remaining() ) * 2);
++            newPrevious.put(previous);
++            newPrevious.put(buffer);
++            newPrevious.flip();
++            previous = newPrevious;
 +        }
++        return previous;
 +    }
- 
++    
 +    /**
 +     * Process a read ByteBuffer over a secured connection, or during the SSL/TLS Handshake.
 +     * 
 +     * @param session The session we are processing a read for
 +     * @param readBuffer The data we get from the channel
 +     * @throws SSLException If the unwrapping or handshaking failed
 +     */
 +    public void processRead(AbstractIoSession session, ByteBuffer readBuffer) throws SSLException {
-         if (session.isConnectedSecured()) {
-             // Unwrap the incoming data
-             while (readBuffer.hasRemaining()) {
-                 processUnwrap(session, readBuffer);
-             }
++        ByteBuffer tempBuffer;
++        
++        if (previous != null) {
++            tempBuffer = accumulate(readBuffer);
 +        } else {
-             // Process the SSL handshake now
-             processHandShake(session, readBuffer);
-         }
-     }
- 
-     /**
-      * Unwrap a SSL/TLS message. The message might not be encrypted (if we are processing a Handshake message or an
-      * Alert message).
-      */
-     private void processUnwrap(AbstractIoSession session, ByteBuffer inBuffer) throws SSLException {
-         // Blind guess : once uncompressed, the resulting buffer will be 3 times bigger
-         ByteBuffer appBuffer = ByteBuffer.allocate(inBuffer.limit() * 3);
-         SSLEngineResult result = unwrap(inBuffer, appBuffer);
- 
-         switch (result.getStatus()) {
-         case OK:
-             // Ok, go through the chain now
-             appBuffer.flip();
-             session.processMessageReceived(appBuffer);
-             break;
- 
-         case CLOSED:
-             // This was a Alert Closure message. Process it
-             processClosed(result);
- 
-             break;
-         }
-     }
- 
-     /**
-      * Process the SSL/TLS Alert Closure message
-      */
-     private void processClosed(SSLEngineResult result) throws SSLException {
-         // We have received a Alert_CLosure message, we will have to do a wrap
-         HandshakeStatus hsStatus = result.getHandshakeStatus();
- 
-         if (hsStatus == HandshakeStatus.NEED_WRAP) {
-             // We need to send back the Alert Closure message
-             if (LOGGER.isDebugEnabled()) {
-                 LOGGER.debug("{} processing the NEED_WRAP state", session);
-             }
- 
-             int capacity = sslEngine.getSession().getPacketBufferSize();
-             ByteBuffer outBuffer = ByteBuffer.allocate(capacity);
-             session.changeState(SessionState.CONNECTED);
- 
-             // Loop until the SSLEngine has nothing more to produce
-             while (!sslEngine.isOutboundDone()) {
-                 sslEngine.wrap(EMPTY_BUFFER, outBuffer);
-                 outBuffer.flip();
- 
-                 // Get out of the Connected state
-                 WriteRequest writeRequest = new DefaultWriteRequest(outBuffer);
-                 session.enqueueWriteRequest(writeRequest);
-             }
-         }
-         session.close(false);
-     }
- 
-     /**
-      * Process the SLL/TLS Handshake. We may enter in this method more than once, as the handshake is a dialogue between
-      * the client and the server.
-      */
-     private void processHandShake(IoSession session, ByteBuffer inBuffer) throws SSLException {
-         // Start the Handshake if we aren't already processing a HandShake
-         // and switch to the SECURING state
-         HandshakeStatus hsStatus = sslEngine.getHandshakeStatus();
- 
-         // Initilize the session status when we enter into the Handshake process.
-         // Not that we don't call the SSLEngine.beginHandshake() method :
-         // It's implicitely done internally by the unwrap() method.
-         if (hsStatus == HandshakeStatus.NOT_HANDSHAKING) {
-             session.changeState(SessionState.SECURING);
++            tempBuffer = readBuffer;
 +        }
- 
-         SSLEngineResult result = null;
- 
-         // If the SSLEngine has not be started, then the status will be NOT_HANDSHAKING
-         // We loop until we reach the FINISHED state
-         while (hsStatus != HandshakeStatus.FINISHED) {
-             if (hsStatus == HandshakeStatus.NEED_TASK) {
-                 hsStatus = processTasks(sslEngine);
-             } else if (hsStatus == HandshakeStatus.NEED_WRAP) {
-                 if (LOGGER.isDebugEnabled()) {
-                     LOGGER.debug("{} processing the NEED_WRAP state", session);
++        
++        
++        boolean done = false;
++        SSLEngineResult result;
++        ByteBuffer appBuffer = ByteBuffer.allocateDirect(sslEngine.getSession().getApplicationBufferSize());
++        
++        HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
++        while (!done) {
++            switch (handshakeStatus) {
++            case NEED_UNWRAP:
++            case NOT_HANDSHAKING:
++            case FINISHED:
++              result = sslEngine.unwrap(tempBuffer, appBuffer);
++              handshakeStatus = result.getHandshakeStatus();
++
++              switch (result.getStatus()) {
++              case BUFFER_UNDERFLOW:
++                  /* we need more data */
++                  done = true;
++                  break;
++              case BUFFER_OVERFLOW:
++                  /* resize output buffer */
++                  appBuffer = ByteBuffer.allocateDirect(appBuffer.capacity() * 2);
++                  break;
++              case OK:
++                  if ((handshakeStatus == HandshakeStatus.NOT_HANDSHAKING) &&
++                      (result.bytesProduced() > 0)) {
++                      appBuffer.flip();
++                      session.processMessageReceived(appBuffer);
++                  }
++              }
++              break;
++            case NEED_TASK:
++                Runnable task;
++                
++                while ((task = sslEngine.getDelegatedTask()) != null) {
++                    task.run();
 +                }
- 
-                 // Create an insanely wide buffer, as the SSLEngine requires it
-                 int capacity = sslEngine.getSession().getPacketBufferSize();
-                 ByteBuffer outBuffer = ByteBuffer.allocate(capacity);
- 
-                 boolean completed = false;
- 
-                 // Loop until we are able to wrap the message (we may have
-                 // to increase the buffer size more than once.
-                 while (!completed) {
-                     result = sslEngine.wrap(EMPTY_BUFFER, outBuffer);
- 
-                     switch (result.getStatus()) {
-                     case OK:
-                     case CLOSED:
-                         completed = true;
-                         break;
- 
-                     case BUFFER_OVERFLOW:
-                         // Increase the target buffer size
-                         outBuffer = ByteBuffer.allocate(outBuffer.capacity() + 4096);
-                         break;
-                     }
-                 }
- 
-                 // Done. We can now push this buffer into the write queue.
-                 outBuffer.flip();
-                 WriteRequest writeRequest = new DefaultWriteRequest(inBuffer);
-                 writeRequest.setMessage(outBuffer);
-                 session.enqueueWriteRequest(writeRequest);
-                 hsStatus = result.getHandshakeStatus();
- 
-                 // Nothing more to wrap : get out.
-                 // Note to self : we can probably use only one ByteBuffer for the
-                 // multiple wrapped messages. (see https://issues.apache.org/jira/browse/DIRMINA-878)
-                 if (hsStatus != HandshakeStatus.NEED_WRAP) {
++                handshakeStatus = sslEngine.getHandshakeStatus();
++                break;
++            case NEED_WRAP:
++                result = sslEngine.wrap(EMPTY_BUFFER, appBuffer);
++                handshakeStatus = result.getHandshakeStatus();
++                switch (result.getStatus()) {
++                case  BUFFER_OVERFLOW:
++                    appBuffer = ByteBuffer.allocateDirect(appBuffer.capacity() * 2);
 +                    break;
-                 }
-             } else if ((hsStatus == HandshakeStatus.NEED_UNWRAP) || (hsStatus == HandshakeStatus.NOT_HANDSHAKING)) {
-                 // We cover the ongoing handshake (NEED_UNWRAP) and
-                 // the initial call to the handshake (NOT_HANDSHAKING)
-                 result = unwrap(inBuffer, HANDSHAKE_BUFFER);
- 
-                 if (result.getStatus() == Status.BUFFER_UNDERFLOW) {
-                     // Read more data
++                case BUFFER_UNDERFLOW:
++                    done = true;
++                    break;
++                case CLOSED:
++                case OK:
++                    appBuffer.flip();
++                    WriteRequest writeRequest = new DefaultWriteRequest(readBuffer);
++                    writeRequest.setMessage(appBuffer);
++                    session.enqueueWriteRequest(writeRequest);
 +                    break;
-                 } else {
-                     hsStatus = result.getHandshakeStatus();
 +                }
 +            }
 +        }
- 
-         if (hsStatus == HandshakeStatus.FINISHED) {
-             // The handshake has been completed. We can change the session's state.
-             if (LOGGER.isDebugEnabled()) {
-                 LOGGER.debug("{} processing the FINISHED state", session);
-             }
- 
-             session.changeState(SessionState.SECURED);
++        if (tempBuffer.remaining() > 0) {
++            previous = duplicate(tempBuffer);
++        } else {
++            previous = null;
 +        }
-     }
++        readBuffer.clear();
++     }
 +
 +    /**
 +     * Process the application data encryption for a session.
 +     * 
 +     * @param session The session sending encrypted data to the peer.
 +     * @param message The message to encrypt
 +     * @param writeQueue The queue in which the encrypted buffer will be written
 +     * @return The written WriteRequest
 +     */
 +    /** No qualifier */
 +    WriteRequest processWrite(IoSession session, Object message, Queue<WriteRequest> writeQueue) {
 +        ByteBuffer buf = (ByteBuffer) message;
 +        ByteBuffer appBuffer = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
 +
 +        try {
 +            while (true) {
 +                // Encrypt the message
 +                SSLEngineResult result = sslEngine.wrap(buf, appBuffer);
 +
 +                switch (result.getStatus()) {
 +                case BUFFER_OVERFLOW:
 +                    // Increase the buffer size as needed
 +                    appBuffer = ByteBuffer.allocate(appBuffer.capacity() + 4096);
 +                    break;
 +
 +                case BUFFER_UNDERFLOW:
 +                case CLOSED:
 +                    break;
 +
 +                case OK:
 +                    // We are done. Flip the buffer and push it to the write queue.
 +                    appBuffer.flip();
 +                    WriteRequest request = new DefaultWriteRequest(appBuffer);
 +
 +                    return request;
 +                }
 +            }
 +        } catch (SSLException se) {
 +            throw new IllegalStateException(se.getMessage());
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/mina/blob/31767ad4/core/src/test/java/org/apache/mina/transport/nio/SslTest.java
----------------------------------------------------------------------
diff --cc core/src/test/java/org/apache/mina/transport/nio/SslTest.java
index 019fb47,0000000..fad8428
mode 100644,000000..100644
--- a/core/src/test/java/org/apache/mina/transport/nio/SslTest.java
+++ b/core/src/test/java/org/apache/mina/transport/nio/SslTest.java
@@@ -1,215 -1,0 +1,214 @@@
 +/*
 + *  Licensed to the Apache Software Foundation (ASF) under one
 + *  or more contributor license agreements.  See the NOTICE file
 + *  distributed with this work for additional information
 + *  regarding copyright ownership.  The ASF licenses this file
 + *  to you under the Apache License, Version 2.0 (the
 + *  "License"); you may not use this file except in compliance
 + *  with the License.  You may obtain a copy of the License at
 + *
 + *    http://www.apache.org/licenses/LICENSE-2.0
 + *
 + *  Unless required by applicable law or agreed to in writing,
 + *  software distributed under the License is distributed on an
 + *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + *  KIND, either express or implied.  See the License for the
 + *  specific language governing permissions and limitations
 + *  under the License.
 + *
 + */
 +package org.apache.mina.transport.nio;
 +
 +import static org.junit.Assert.assertTrue;
 +
 +import java.io.BufferedReader;
 +import java.io.IOException;
 +import java.io.InputStreamReader;
 +import java.net.InetAddress;
 +import java.net.InetSocketAddress;
 +import java.net.Socket;
 +import java.nio.ByteBuffer;
 +import java.nio.charset.Charset;
 +import java.security.GeneralSecurityException;
 +import java.security.KeyStore;
 +import java.security.Security;
 +import java.util.concurrent.CountDownLatch;
 +import java.util.concurrent.TimeUnit;
 +
 +import javax.net.ssl.KeyManagerFactory;
 +import javax.net.ssl.SSLContext;
 +import javax.net.ssl.SSLSocketFactory;
 +import javax.net.ssl.TrustManagerFactory;
 +
 +import org.apache.mina.api.AbstractIoHandler;
 +import org.apache.mina.api.IoSession;
 +import org.apache.mina.transport.nio.NioTcpServer;
 +import org.junit.Ignore;
 +import org.junit.Test;
 +
 +/**
 + * Test a SSL session where the connection is established and closed twice. It should be
 + * processed correctly (Test for DIRMINA-650)
 + *
 + * @author <a href="http://mina.apache.org">Apache MINA Project</a>
 + */
 +public class SslTest {
 +    private static Exception clientError = null;
 +
 +    private static InetAddress address;
 +
 +    private static SSLSocketFactory factory;
 +
 +    /** A JVM independant KEY_MANAGER_FACTORY algorithm */
 +    private static final String KEY_MANAGER_FACTORY_ALGORITHM;
 +
 +    static {
 +        String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
 +        if (algorithm == null) {
 +            algorithm = KeyManagerFactory.getDefaultAlgorithm();
 +        }
 +
 +        KEY_MANAGER_FACTORY_ALGORITHM = algorithm;
 +    }
 +
 +    private static class TestHandler extends AbstractIoHandler {
 +        public void messageReceived(IoSession session, Object message) {
 +            String line = Charset.defaultCharset().decode((ByteBuffer) message).toString();
 +
 +            if (line.startsWith("hello")) {
 +                System.out.println("Server got: 'hello', waiting for 'send'");
 +            } else if (line.startsWith("send")) {
 +                System.out.println("Server got: 'send', sending 'data'");
 +                session.write(Charset.defaultCharset().encode("data\n"));
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Starts a Server with the SSL Filter and a simple text line 
 +     * protocol codec filter
 +     */
 +    private static int startServer() throws Exception {
 +        NioTcpServer server = new NioTcpServer();
 +
 +        server.setReuseAddress(true);
 +        server.getSessionConfig().setSslContext(createSSLContext());
 +        server.setIoHandler(new TestHandler());
 +        server.bind(new InetSocketAddress(0));
 +        return server.getServerSocketChannel().socket().getLocalPort();
 +    }
 +
 +    /**
 +     * Starts a client which will connect twice using SSL
 +     */
 +    private static void startClient(int port) throws Exception {
 +        address = InetAddress.getByName("localhost");
 +
 +        SSLContext context = createSSLContext();
 +        factory = context.getSocketFactory();
 +
 +        connectAndSend(port);
 +
 +        // This one will throw a SocketTimeoutException if DIRMINA-650 is not fixed
 +        connectAndSend(port);
 +    }
 +
 +    private static void connectAndSend(int port) throws Exception {
 +        Socket parent = new Socket(address, port);
 +        Socket socket = factory.createSocket(parent, address.getCanonicalHostName(), port, false);
 +
 +        System.out.println("Client sending: hello");
 +        socket.getOutputStream().write("hello                      \n".getBytes());
 +        socket.getOutputStream().flush();
 +        socket.setSoTimeout(10000);
 +
 +        System.out.println("Client sending: send");
 +        socket.getOutputStream().write("send\n".getBytes());
 +        socket.getOutputStream().flush();
 +
 +        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 +        String line = in.readLine();
 +        System.out.println("Client got: " + line);
 +        socket.close();
 +
 +    }
 +
 +    private static SSLContext createSSLContext() throws IOException, GeneralSecurityException {
 +        char[] passphrase = "password".toCharArray();
 +
 +        SSLContext ctx = SSLContext.getInstance("TLS");
 +        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
 +        TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
 +
 +        KeyStore ks = KeyStore.getInstance("JKS");
 +        KeyStore ts = KeyStore.getInstance("JKS");
 +
 +        ks.load(SslTest.class.getResourceAsStream("keystore.sslTest"), passphrase);
 +        ts.load(SslTest.class.getResourceAsStream("truststore.sslTest"), passphrase);
 +
 +        kmf.init(ks, passphrase);
 +        tmf.init(ts);
 +        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
 +
 +        return ctx;
 +    }
 +
 +    @Test
-     @Ignore("SslHelper needs more attention for big messages")
++    @Ignore("check for fragmentation")
 +    public void testSSL() throws Exception {
 +        final int port = startServer();
 +
 +        Thread t = new Thread() {
 +            public void run() {
 +                try {
 +                    startClient(port);
 +                } catch (Exception e) {
 +                    clientError = e;
 +                }
 +            }
 +        };
 +        t.start();
 +        t.join();
 +        if (clientError != null)
 +            throw clientError;
 +    }
 +
 +    @Test
-     @Ignore("SslHelper needs more attention for big messages")
 +    public void testBigMessage() throws IOException, GeneralSecurityException, InterruptedException {
 +        final CountDownLatch counter = new CountDownLatch(1);
 +        NioTcpServer server = new NioTcpServer();
 +        final int messageSize = 1 * 1024 * 1024;
 +
 +        /*
 +         * Server
 +         */
 +        server.setReuseAddress(true);
 +        server.getSessionConfig().setSslContext(createSSLContext());
 +        server.setIoHandler(new AbstractIoHandler() {
 +            private int receivedSize = 0;
 +
 +            /**
 +             * {@inheritedDoc}
 +             */
 +            @Override
 +            public void messageReceived(IoSession session, Object message) {
 +                receivedSize += ((ByteBuffer) message).remaining();
-                 if (receivedSize == 0) {
++                if (receivedSize == messageSize) {
 +                    counter.countDown();
 +                }
 +            }
 +        });
 +        server.bind(new InetSocketAddress(0));
 +        int port = server.getServerSocketChannel().socket().getLocalPort();
 +
 +        /*
 +         * Client
 +         */
 +        Socket socket = server.getSessionConfig().getSslContext().getSocketFactory().createSocket("localhost", port);
 +        socket.getOutputStream().write(new byte[messageSize]);
 +        socket.getOutputStream().flush();
 +        socket.close();
 +        assertTrue(counter.await(10, TimeUnit.SECONDS));
 +
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/mina/blob/31767ad4/core/src/test/resources/org/apache/mina/transport/nio/keystore.cert
----------------------------------------------------------------------
diff --cc core/src/test/resources/org/apache/mina/transport/nio/keystore.cert
index 0000000,0000000..d34502d
new file mode 100644
Binary files differ

http://git-wip-us.apache.org/repos/asf/mina/blob/31767ad4/core/src/test/resources/org/apache/mina/transport/nio/keystore.sslTest
----------------------------------------------------------------------
diff --cc core/src/test/resources/org/apache/mina/transport/nio/keystore.sslTest
index 0000000,0000000..36190ba
new file mode 100644
Binary files differ

http://git-wip-us.apache.org/repos/asf/mina/blob/31767ad4/core/src/test/resources/org/apache/mina/transport/nio/truststore.sslTest
----------------------------------------------------------------------
diff --cc core/src/test/resources/org/apache/mina/transport/nio/truststore.sslTest
index 0000000,0000000..48c5963
new file mode 100644
Binary files differ

http://git-wip-us.apache.org/repos/asf/mina/blob/31767ad4/core/src/test/resources/org/apache/mina/transport/tcp/keystore.cert
----------------------------------------------------------------------
diff --cc core/src/test/resources/org/apache/mina/transport/tcp/keystore.cert
index d34502d,d34502d..0000000
deleted file mode 100644,100644
Binary files differ

http://git-wip-us.apache.org/repos/asf/mina/blob/31767ad4/core/src/test/resources/org/apache/mina/transport/tcp/keystore.sslTest
----------------------------------------------------------------------
diff --cc core/src/test/resources/org/apache/mina/transport/tcp/keystore.sslTest
index 36190ba,36190ba..0000000
deleted file mode 100644,100644
Binary files differ

http://git-wip-us.apache.org/repos/asf/mina/blob/31767ad4/core/src/test/resources/org/apache/mina/transport/tcp/truststore.sslTest
----------------------------------------------------------------------
diff --cc core/src/test/resources/org/apache/mina/transport/tcp/truststore.sslTest
index 48c5963,48c5963..0000000
deleted file mode 100644,100644
Binary files differ