diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG0.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG0.java index ec5653245..c01ea6a01 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG0.java +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG0.java @@ -94,11 +94,6 @@ */ protected Thread mDecodeThread = null; - /** - * Captured error state - */ - protected SSLException mPendingError = null; - /** * Instantiates a new handler * @@ -207,6 +202,15 @@ protected void receive_loop(NextFilter next, IoBuffer message) throws SSLExcepti } } + // The message is null when we have had an exception + if (message==null) { + if ( mPendingError != null ) { + throw mPendingError; + } else { + throw new IllegalStateException("closed"); + } + } + IoBuffer source = message; // No need to fo for another loop if the message is empty @@ -450,26 +454,6 @@ synchronized protected boolean write_user_loop(NextFilter next, WriteRequest req return false; } - /** - * Attempts to generate a handshake message and write the data to the IoSession - * - * @param next - * - * @return {@code true} if a message was generated and written - * - * @throws SSLException - */ - synchronized protected boolean write_handshake(NextFilter next) throws SSLException { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("{} write_handshake() - internal", toString()); - } - - IoBuffer source = ZERO; - IoBuffer dest = allocate_encode_buffer(source.remaining()); - - return write_handshake_loop(next, source, dest); - } - /** * Attempts to generate a handshake message and write the data to the IoSession. *
@@ -484,6 +468,7 @@ synchronized protected boolean write_handshake(NextFilter next) throws SSLExcept
*
* @throws SSLException
*/
+ @Override
@SuppressWarnings("incomplete-switch")
protected boolean write_handshake_loop(NextFilter next, IoBuffer source, IoBuffer dest) throws SSLException {
if (mOutboundClosing && mEngine.isOutboundDone()) {
@@ -675,26 +660,6 @@ synchronized public void close(NextFilter next, boolean linger) throws SSLExcept
}
}
- /**
- * Process the pending error and loop to send the associated alert if we have some.
- *
- * @param next The next filter in the chain
- * @throws SSLException The rethrown pending error
- */
- synchronized protected void throw_pending_error(NextFilter next) throws SSLException {
- SSLException sslException = mPendingError;
-
- if (sslException != null) {
- // Loop to send back the alert messages
- receive_loop(next, null);
-
- mPendingError = null;
-
- // And finally rethrow the exception
- throw sslException;
- }
- }
-
/**
* Store any error we've got during the handshake or message handling
*
diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG1.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG1.java
index b2eae3fc2..563008872 100644
--- a/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG1.java
+++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG1.java
@@ -29,6 +29,7 @@
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
+
import java.nio.BufferOverflowException;
import java.util.ArrayList;
import java.util.Deque;
@@ -69,7 +70,7 @@
/**
* Enable asynchronous tasks. Default to false.
*/
- static protected final boolean ENABLE_ASYNC_TASKS = false;
+ static protected final boolean ENABLE_ASYNC_TASKS = true;
/**
* Indicates whether the first handshake was completed
@@ -111,11 +112,6 @@
*/
protected final Deque
@@ -524,6 +509,7 @@ synchronized protected boolean write_handshake(NextFilter next) throws SSLExcept
*
* @throws SSLException
*/
+ @Override
@SuppressWarnings("incomplete-switch")
protected boolean write_handshake_loop(NextFilter next, IoBuffer source, IoBuffer dest) throws SSLException {
if (mOutboundClosing && mEngine.isOutboundDone()) {
@@ -728,26 +714,6 @@ synchronized protected void close_start(NextFilter next, boolean linger) throws
}
}
- /**
- * Process the pending error and loop to send the associated alert if we have some.
- *
- * @param next The next filter in the chain
- * @throws SSLException The rethrown pending error
- */
- synchronized protected void throw_pending_error(NextFilter next) throws SSLException {
- SSLException sslException = mPendingError;
- if (sslException != null) {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("{} throw_pending_error() - throwing pending error");
- }
- // Loop to send back the alert messages
- receive_loop(next, null);
- mPendingError = null;
- // And finally rethrow the exception
- throw sslException;
- }
- }
-
/**
* Store any error we've got during the handshake or message handling
*
diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java
index 1e6d13735..21426cc76 100644
--- a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java
+++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java
@@ -26,6 +26,7 @@
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.IoFilter.NextFilter;
@@ -93,6 +94,11 @@ public abstract class SslHandler {
*/
protected IoBuffer mDecodeBuffer;
+ /**
+ * Captured error state
+ */
+ protected SSLException mPendingError = null;
+
/**
* Instantiates a new handler
*
@@ -176,6 +182,22 @@ public SslHandler(SSLEngine p, Executor e, IoSession s) {
*/
abstract public void close(NextFilter next, final boolean linger) throws SSLException;
+ /**
+ * Attempts to generate a handshake message and write the data to the IoSession.
+ *
+ * If FAST_HANDSHAKE is enabled, this method will recursively loop in order to
+ * combine multiple messages into one buffer.
+ *
+ * @param next
+ * @param source
+ * @param dest
+ *
+ * @return {@code true} if a message was generated and written
+ *
+ * @throws SSLException
+ */
+ protected abstract boolean write_handshake_loop(NextFilter next, IoBuffer source, IoBuffer dest) throws SSLException;
+
/**
* {@inheritDoc}
*/
@@ -281,4 +303,48 @@ protected IoBuffer allocate_app_buffer(int estimate) {
int packets = 1 + (estimate / session.getPacketBufferSize());
return IoBuffer.allocate(packets * session.getApplicationBufferSize());
}
+
+
+ /**
+ * Process the pending error and loop to send the associated alert if we have some.
+ *
+ * @param next The next filter in the chain
+ * @throws SSLException The rethrown pending error
+ */
+ synchronized protected void throw_pending_error(NextFilter next) throws SSLException {
+ SSLException sslException = mPendingError;
+
+ if (sslException != null) {
+ // Errors are fatal: the pending error stays set and is rethrown on every subsequent call.
+ if (mEngine.isInboundDone() && mEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("{} throw_pending_error() - handshake needs wrap, invoking write", toString());
+ }
+
+ write_handshake(next);
+ }
+
+ throw sslException;
+ }
+ }
+
+ /**
+ * Attempts to generate a handshake message and write the data to the IoSession
+ *
+ * @param next
+ *
+ * @return {@code true} if a message was generated and written
+ *
+ * @throws SSLException
+ */
+ synchronized protected boolean write_handshake(NextFilter next) throws SSLException {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("{} write_handshake() - internal", toString());
+ }
+
+ IoBuffer source = ZERO;
+ IoBuffer dest = allocate_encode_buffer(source.remaining());
+
+ return write_handshake_loop(next, source, dest);
+ }
}
diff --git a/mina-core/src/test/java/org/apache/mina/filter/ssl/SslFilterTest.java b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslFilterTest.java
new file mode 100644
index 000000000..1d3d4dcf8
--- /dev/null
+++ b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslFilterTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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.filter.ssl;
+
+import java.net.InetSocketAddress;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.mina.core.buffer.IoBuffer;
+import org.apache.mina.core.future.IoFuture;
+import org.apache.mina.core.service.IoAcceptor;
+import org.apache.mina.core.service.IoConnector;
+import org.apache.mina.core.service.IoHandlerAdapter;
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
+import org.apache.mina.transport.socket.nio.NioSocketConnector;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.assertTrue;
+
+public class SslFilterTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SslFilterTest.class);
+
+ @Test
+ public void transferLargeData() throws Exception {
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+
+ KeyStore ks = KeyStore.getInstance("JKS");
+ KeyStore ts = KeyStore.getInstance("JKS");
+
+ final char[] password = "password".toCharArray();
+
+ ks.load(this.getClass().getResourceAsStream("keystore.jks"), password);
+ ts.load(this.getClass().getResourceAsStream("truststore.jks"), password);
+
+ kmf.init(ks, password);
+ tmf.init(ts);
+
+ final SSLContext contextClient = SSLContext.getInstance("TLSv1.3");
+ contextClient.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
+
+ final MySslFilter filterClient = new MySslFilter(contextClient);
+ filterClient.setEnabledCipherSuites(new String[] { "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384" });
+ filterClient.setEnabledProtocols(new String[] { "TLSv1.3" });
+ filterClient.setUseNonBlockingPipeline( true );
+
+
+ final IoConnector socketConnector = new NioSocketConnector();
+
+ socketConnector.getFilterChain().addFirst("ssl", filterClient);
+ socketConnector.setHandler(new DebugFilter());
+
+ final SSLContext contextServer = SSLContext.getInstance("TLSv1.3");
+ contextServer.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
+
+ final MySslFilter filterServer = new MySslFilter(contextServer);
+ filterServer.setEnabledCipherSuites(new String[] { "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384" });
+ filterServer.setEnabledProtocols(new String[] { "TLSv1.3" });
+ filterServer.setUseNonBlockingPipeline( false );
+
+ final IoAcceptor socketAcceptor = new NioSocketAcceptor();
+
+ socketAcceptor.getFilterChain().addFirst("ssl", filterServer);
+ CompareFilter filter = new CompareFilter();
+ socketAcceptor.setHandler(filter);
+
+ try {
+ socketAcceptor.bind(new InetSocketAddress("localhost", 0));
+
+ final IoFuture connectFuture = socketConnector.connect(socketAcceptor.getLocalAddress());
+ connectFuture.awaitUninterruptibly();
+
+ final IoSession clientSocket = connectFuture.getSession();
+ try {
+ SSLEngine sslClient = filterClient.getEngine();
+ LOG.error("client packet size " + sslClient.getSession().getPacketBufferSize());
+ LOG.error("client app buffer size " + sslClient.getSession().getApplicationBufferSize());
+ for (int msgSize = 61000; msgSize < 80000; msgSize++) {
+ LOG.error("Trying " + msgSize);
+ CountDownLatch received = new CountDownLatch(1);
+ filter.init(received, msgSize);
+ IoBuffer data = createMosaicRequest(msgSize);
+ clientSocket.write(data).await();
+ data.free();
+ assertTrue(received.await(5, TimeUnit.SECONDS));
+ assertNoException(filter.getFailure());
+ }
+ } finally {
+ clientSocket.closeNow().await();
+ }
+ } finally {
+ socketAcceptor.unbind();
+ socketAcceptor.dispose();
+ socketConnector.dispose();
+ }
+ }
+
+ private static void assertNoException(Throwable t) {
+ if (t != null) {
+ throw new AssertionError(t);
+ }
+ }
+
+ private static class DebugFilter extends IoHandlerAdapter {
+
+ @Override
+ public void messageReceived(IoSession session, Object message) throws Exception {
+ IoBuffer b = IoBuffer.class.cast(message);
+ LOG.error("Client: received clear-text message\n" + b.getHexDump(true));
+ }
+ }
+
+ private static class CompareFilter extends IoHandlerAdapter {
+
+ private CountDownLatch done;
+
+ private int needBytes;
+
+ private IoBuffer acc;
+
+ private Throwable failure;
+
+ CompareFilter() {
+ super();
+ }
+
+ void init(CountDownLatch done, int size) {
+ if (this.acc != null) {
+ this.acc.free();
+ }
+ this.done = done;
+ this.needBytes = size;
+ this.acc = IoBuffer.allocate(size);
+ }
+
+ Throwable getFailure() {
+ return failure;
+ }
+
+ @Override
+ public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
+ LOG.error("Remote: got exception", cause);
+ failure = cause;
+ done.countDown();
+ }
+
+ @Override
+ public void messageReceived(IoSession session, Object message) throws Exception {
+ if (needBytes <= 0) {
+ LOG.error("Remote: got spurious data");
+ return;
+ }
+ IoBuffer b = IoBuffer.class.cast(message);
+ int received = b.remaining();
+ if (received > needBytes) {
+ LOG.error("Remote: got " + received + " bytes, expected only " + needBytes);
+ byte[] data = new byte[needBytes];
+ b.get(data);
+ acc.put(data);
+ } else {
+ LOG.error("Remote: got " + received + " bytes");
+ acc.put(b);
+ }
+ needBytes -= received;
+ LOG.error("Remote: still need " + needBytes + " bytes");
+ if (needBytes <= 0) {
+ int i = 0;
+ int j = 0;
+ acc.flip();
+ while (acc.hasRemaining()) {
+ int val = acc.get() & 0xFF;
+ int cmp = i % 16367 == 0 ? j++ : 0x11;
+ if (val != cmp) {
+ LOG.error("Remote: data mismatch at index " + i);
+ break;
+ }
+ i++;
+ if (acc.hasRemaining()) {
+ val = acc.get() & 0xFF;
+ cmp = i % 16367 == 0 ? j++ : 0xAA;
+ if (val != cmp) {
+ LOG.error("Remote: data mismatch at index " + i);
+ break;
+ }
+ i++;
+ }
+ }
+ done.countDown();
+ }
+ }
+ }
+
+ public static IoBuffer createMosaicRequest(int size) {
+ IoBuffer message = IoBuffer.allocate(size);
+ int i = 0;
+ int j = 0;
+ while (message.hasRemaining()) {
+ if (i++ % 16367 == 0) {
+ message.put((byte) j);
+ j++;
+ } else {
+ message.put((byte) 0x11);
+ }
+ if (message.hasRemaining()) {
+ if (i++ % 16367 == 0) {
+ message.put((byte) j);
+ j++;
+ } else {
+ message.put((byte) 0xAA);
+ }
+ }
+ }
+ message.flip();
+ return message;
+ }
+
+ private static class MySslFilter extends SslFilter {
+
+ MySslFilter(SSLContext sslContext) {
+ super(sslContext);
+ }
+
+ private SSLEngine ssl;
+
+ @Override
+ protected SSLEngine createEngine(IoSession session, InetSocketAddress addr) {
+ ssl = super.createEngine(session, addr);
+ return ssl;
+ }
+
+ SSLEngine getEngine() {
+ return ssl;
+ }
+ }
+}