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:37 UTC
[1/3] git commit: The readBuffer is now only cleared for not secured
sessions
Updated Branches:
refs/heads/trunk dbbdf9ea0 -> 31767ad4c
The readBuffer is now only cleared for not secured sessions
Project: http://git-wip-us.apache.org/repos/asf/mina/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina/commit/8bc3c404
Tree: http://git-wip-us.apache.org/repos/asf/mina/tree/8bc3c404
Diff: http://git-wip-us.apache.org/repos/asf/mina/diff/8bc3c404
Branch: refs/heads/trunk
Commit: 8bc3c4047e2af669c6c8cbdc4d05ad8c63e914ef
Parents: 2a1dbba
Author: Emmanuel Lécharny <el...@apache.org>
Authored: Fri May 3 16:05:24 2013 +0200
Committer: Emmanuel Lécharny <el...@apache.org>
Committed: Fri May 3 16:05:24 2013 +0200
----------------------------------------------------------------------
.../mina/transport/nio/tcp/NioTcpSession.java | 8 +++++---
1 files changed, 5 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina/blob/8bc3c404/core/src/main/java/org/apache/mina/transport/nio/tcp/NioTcpSession.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/mina/transport/nio/tcp/NioTcpSession.java b/core/src/main/java/org/apache/mina/transport/nio/tcp/NioTcpSession.java
index f06fda7..cd9e8d7 100644
--- a/core/src/main/java/org/apache/mina/transport/nio/tcp/NioTcpSession.java
+++ b/core/src/main/java/org/apache/mina/transport/nio/tcp/NioTcpSession.java
@@ -290,9 +290,6 @@ public class NioTcpSession extends AbstractIoSession implements SelectorListener
try {
LOG.debug("readable session : {}", this);
- // First reset the buffer from what it contained before
- readBuffer.clear();
-
// Read everything we can up to the buffer size
final int readCount = ((SocketChannel) channel).read(readBuffer);
@@ -318,9 +315,14 @@ public class NioTcpSession extends AbstractIoSession implements SelectorListener
}
sslHelper.processRead(this, readBuffer);
+
+ // We don't clear the buffer. It has been done by the sslHelper
} else {
// Plain message, not encrypted : go directly to the chain
processMessageReceived(readBuffer);
+
+ // And now, clear the buffer
+ readBuffer.clear();
}
// Update the session idle status
[3/3] git commit: Merge branch 'ssl-experiment' into trunk
Posted by je...@apache.org.
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
[2/3] git commit: Rewrote the SslHelper to be handshake status
driven. Need attention to buffer management. Supports rehandshaking but needs
attention to previous and future queued messages. Consider as a first attempt
only
Posted by je...@apache.org.
Rewrote the SslHelper to be handshake status driven.
Need attention to buffer management.
Supports rehandshaking but needs attention to previous and future queued messages.
Consider as a first attempt only
Project: http://git-wip-us.apache.org/repos/asf/mina/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina/commit/2859673d
Tree: http://git-wip-us.apache.org/repos/asf/mina/tree/2859673d
Diff: http://git-wip-us.apache.org/repos/asf/mina/diff/2859673d
Branch: refs/heads/trunk
Commit: 2859673da149e1a23bc9c1c7e187bf1dd61fe904
Parents: 8bc3c40
Author: Jeff MAURY <je...@apache.org>
Authored: Sun May 5 22:33:33 2013 +0200
Committer: Jeff MAURY <je...@apache.org>
Committed: Sun May 5 22:33:33 2013 +0200
----------------------------------------------------------------------
.../java/org/apache/mina/session/SslHelper.java | 302 +++++----------
.../org/apache/mina/transport/tcp/SslTest.java | 5 +-
2 files changed, 103 insertions(+), 204 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina/blob/2859673d/core/src/main/java/org/apache/mina/session/SslHelper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/mina/session/SslHelper.java b/core/src/main/java/org/apache/mina/session/SslHelper.java
index 80af05c..74843a8 100644
--- a/core/src/main/java/org/apache/mina/session/SslHelper.java
+++ b/core/src/main/java/org/apache/mina/session/SslHelper.java
@@ -29,13 +29,11 @@ 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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,6 +81,8 @@ public class SslHelper {
/** 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.
@@ -108,6 +108,10 @@ public class SslHelper {
return sslEngine;
}
+ boolean isHanshaking() {
+ return sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING;
+ }
+
/**
* Initialize the SSL handshake.
*
@@ -155,68 +159,43 @@ public class SslHelper {
}
/**
- * 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.
@@ -226,159 +205,80 @@ public class SslHelper {
* @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);
- }
+ tempBuffer = readBuffer;
}
- 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);
- }
-
- 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.
http://git-wip-us.apache.org/repos/asf/mina/blob/2859673d/core/src/test/java/org/apache/mina/transport/tcp/SslTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/mina/transport/tcp/SslTest.java b/core/src/test/java/org/apache/mina/transport/tcp/SslTest.java
index 8a7aa61..3d33ef0 100644
--- a/core/src/test/java/org/apache/mina/transport/tcp/SslTest.java
+++ b/core/src/test/java/org/apache/mina/transport/tcp/SslTest.java
@@ -155,7 +155,7 @@ public class SslTest {
}
@Test
- @Ignore("SslHelper needs more attention for big messages")
+ @Ignore("check for fragmentation")
public void testSSL() throws Exception {
final int port = startServer();
@@ -175,7 +175,6 @@ public class SslTest {
}
@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();
@@ -195,7 +194,7 @@ public class SslTest {
@Override
public void messageReceived(IoSession session, Object message) {
receivedSize += ((ByteBuffer) message).remaining();
- if (receivedSize == 0) {
+ if (receivedSize == messageSize) {
counter.countDown();
}
}