This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch mina-2.2.X-DIRMINA-1199 in repository https://gitbox.apache.org/repos/asf/mina.git
commit a402bb71196e51936a9df12c6c663956f5f63c53 Author: emmanuel lecharny <[email protected]> AuthorDate: Sun Oct 11 22:58:42 2026 +0200 Fixed the mPendingError handling, moving around some methods to the paren't class --- .../org/apache/mina/filter/ssl/SSLHandlerG0.java | 61 +++-- .../org/apache/mina/filter/ssl/SSLHandlerG1.java | 72 +++--- .../org/apache/mina/filter/ssl/SslHandler.java | 66 ++++++ .../org/apache/mina/filter/ssl/SslFilterTest.java | 263 +++++++++++++++++++++ 4 files changed, 409 insertions(+), 53 deletions(-) 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..3fee601df 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 @@ -97,7 +97,7 @@ import org.apache.mina.core.write.WriteRequest; /** * Captured error state */ - protected SSLException mPendingError = null; + //protected SSLException mPendingError = null; /** * Instantiates a new handler @@ -207,6 +207,15 @@ import org.apache.mina.core.write.WriteRequest; } } + // 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 @@ -459,16 +468,16 @@ import org.apache.mina.core.write.WriteRequest; * * @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); - } +// 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 +493,7 @@ import org.apache.mina.core.write.WriteRequest; * * @throws SSLException */ + @Override @SuppressWarnings("incomplete-switch") protected boolean write_handshake_loop(NextFilter next, IoBuffer source, IoBuffer dest) throws SSLException { if (mOutboundClosing && mEngine.isOutboundDone()) { @@ -681,19 +691,22 @@ import org.apache.mina.core.write.WriteRequest; * @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; - } - } +// 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; +// } +// } /** * 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..ae518bf17 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 org.apache.mina.filter.FilterEvent; 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; @@ -110,11 +111,11 @@ import java.util.concurrent.Executor; * Pending filter events for dispatching */ protected final Deque<FilterEvent> mEventQueue = new ConcurrentLinkedDeque<>(); - - /** - * Captured error state - */ - protected SSLException mPendingError = null; +// +// /** +// * Captured error state +// */ +// protected SSLException mPendingError = null; /** * Instantiates a new handler @@ -237,10 +238,19 @@ import java.util.concurrent.Executor; throw new IllegalStateException("closed"); } } + + // 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 + // No need to go for another loop if the message is empty if (source.remaining() == 0) { return; } @@ -499,16 +509,16 @@ import java.util.concurrent.Executor; * * @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); - } +// 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. @@ -524,6 +534,7 @@ import java.util.concurrent.Executor; * * @throws SSLException */ + @Override @SuppressWarnings("incomplete-switch") protected boolean write_handshake_loop(NextFilter next, IoBuffer source, IoBuffer dest) throws SSLException { if (mOutboundClosing && mEngine.isOutboundDone()) { @@ -734,19 +745,22 @@ import java.util.concurrent.Executor; * @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; - } - } +// 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; +// } +// } /** * 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 java.util.concurrent.Executor; 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 abstract class SslHandler { */ abstract public void close(NextFilter next, final boolean linger) throws SSLException; + /** + * Attempts to generate a handshake message and write the data to the IoSession. + * <p> + * 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 @@ public abstract class SslHandler { 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; + } + } +}
