This is an automated email from the ASF dual-hosted git repository. johnnyv pushed a commit to branch bugfix/DIRMINA1132 in repository https://gitbox.apache.org/repos/asf/mina.git
commit 4c7af5615549cf557141765cefe84f409413130e Author: Jonathan Valliere <john...@apache.org> AuthorDate: Sat Jul 24 11:19:50 2021 -0400 Adds initial ssl2 --- .../mina/filter/ssl2/EncryptedWriteRequest.java | 32 +++ .../org/apache/mina/filter/ssl2/SSL2Filter.java | 248 ++++++++++++++++ .../org/apache/mina/filter/ssl2/SSL2Handler.java | 217 ++++++++++++++ .../org/apache/mina/filter/ssl2/SSL2HandlerG0.java | 318 +++++++++++++++++++++ .../org/apache/mina/util/BasicThreadFactory.java | 62 ++++ .../java/org/apache/mina/util/StackInspector.java | 78 +++++ .../apache/mina/filter/ssl2/SSL2SimpleTest.java | 114 ++++++++ .../org/apache/mina/filter/ssl2/keystore.sslTest | Bin 0 -> 1368 bytes .../org/apache/mina/filter/ssl2/truststore.sslTest | Bin 0 -> 654 bytes 9 files changed, 1069 insertions(+) diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl2/EncryptedWriteRequest.java b/mina-core/src/main/java/org/apache/mina/filter/ssl2/EncryptedWriteRequest.java new file mode 100644 index 0000000..91fabc7 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl2/EncryptedWriteRequest.java @@ -0,0 +1,32 @@ +package org.apache.mina.filter.ssl2; + +import org.apache.mina.core.future.WriteFuture; +import org.apache.mina.core.write.DefaultWriteRequest; +import org.apache.mina.core.write.WriteRequest; + +public class EncryptedWriteRequest extends DefaultWriteRequest { + + // The original message + private WriteRequest parentRequest; + + public EncryptedWriteRequest(Object encodedMessage, WriteRequest parent) { + super(encodedMessage, null); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEncoded() { + return true; + } + + public WriteRequest getParentRequest() { + return this.parentRequest; + } + + @Override + public WriteFuture getFuture() { + return (this.getParentRequest() != null) ? this.getParentRequest().getFuture() : super.getFuture(); + } +} \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl2/SSL2Filter.java b/mina-core/src/main/java/org/apache/mina/filter/ssl2/SSL2Filter.java new file mode 100644 index 0000000..85d43c6 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl2/SSL2Filter.java @@ -0,0 +1,248 @@ +/* + * 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.ssl2; + +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.filterchain.IoFilterAdapter; +import org.apache.mina.core.filterchain.IoFilterChain; +import org.apache.mina.core.session.AttributeKey; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.util.BasicThreadFactory; +import org.apache.mina.util.StackInspector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An SSL filter that encrypts and decrypts the data exchanged in the session. + * Adding this filter triggers SSL handshake procedure immediately by sending a + * SSL 'hello' message, so you don't need to call {@link #startSsl(IoSession)} + * manually unless you are implementing StartTLS (see below). If you don't want + * the handshake procedure to start immediately, please specify {@code false} as + * {@code autoStart} parameter in the constructor. + * <p> + * This filter uses an {@link SSLEngine} which was introduced in Java 5, so Java + * version 5 or above is mandatory to use this filter. And please note that this + * filter only works for TCP/IP connections. + * + * @author <a href="http://mina.apache.org">Apache MINA Project</a> + */ +public class SSL2Filter extends IoFilterAdapter { + /** + * The logger + */ + protected static final Logger LOGGER = LoggerFactory.getLogger(SSL2Filter.class); + + protected static final Executor EXECUTOR = new ThreadPoolExecutor(2, 4, 100, TimeUnit.MILLISECONDS, + new LinkedBlockingDeque<Runnable>(), new BasicThreadFactory("ssl-exec")); + + protected static final AttributeKey SSL_HANDLER = new AttributeKey(SSL2Filter.class, "handler"); + + protected final SSLContext mContext; + + protected boolean mNeedClientAuth; + + protected boolean mWantClientAuth; + + protected String[] mEnabledCipherSuites; + + protected String[] mEnabledProtocols; + + /** + * Creates a new SSL filter using the specified {@link SSLContext}. + * + * @param sslContext The SSLContext to use + */ + public SSL2Filter(SSLContext sslContext) { + if (sslContext == null) { + throw new IllegalArgumentException("SSLContext is null"); + } + + this.mContext = sslContext; + } + + /** + * @return <tt>true</tt> if the engine will <em>require</em> client + * authentication. This option is only useful to engines in the server + * mode. + */ + public boolean isNeedClientAuth() { + return mNeedClientAuth; + } + + /** + * Configures the engine to <em>require</em> client authentication. This option + * is only useful for engines in the server mode. + * + * @param needClientAuth A flag set when we need to authenticate the client + */ + public void setNeedClientAuth(boolean needClientAuth) { + this.mNeedClientAuth = needClientAuth; + } + + /** + * @return <tt>true</tt> if the engine will <em>request</em> client + * authentication. This option is only useful to engines in the server + * mode. + */ + public boolean isWantClientAuth() { + return mWantClientAuth; + } + + /** + * Configures the engine to <em>request</em> client authentication. This option + * is only useful for engines in the server mode. + * + * @param wantClientAuth A flag set when we want to check the client + * authentication + */ + public void setWantClientAuth(boolean wantClientAuth) { + this.mWantClientAuth = wantClientAuth; + } + + /** + * @return the list of cipher suites to be enabled when {@link SSLEngine} is + * initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.' + */ + public String[] getEnabledCipherSuites() { + return mEnabledCipherSuites; + } + + /** + * Sets the list of cipher suites to be enabled when {@link SSLEngine} is + * initialized. + * + * @param cipherSuites <tt>null</tt> means 'use {@link SSLEngine}'s default.' + */ + public void setEnabledCipherSuites(String[] cipherSuites) { + this.mEnabledCipherSuites = cipherSuites; + } + + /** + * @return the list of protocols to be enabled when {@link SSLEngine} is + * initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.' + */ + public String[] getEnabledProtocols() { + return mEnabledProtocols; + } + + /** + * Sets the list of protocols to be enabled when {@link SSLEngine} is + * initialized. + * + * @param protocols <tt>null</tt> means 'use {@link SSLEngine}'s default.' + */ + public void setEnabledProtocols(String[] protocols) { + this.mEnabledProtocols = protocols; + } + + /** + * Executed just before the filter is added into the chain, we do : + * <ul> + * <li>check that we don't have a SSL filter already present + * <li>we update the next filter + * <li>we create the SSL handler helper class + * <li>and we store it into the session's Attributes + * </ul> + */ + @Override + public void onPreAdd(IoFilterChain parent, String name, NextFilter next) throws Exception { + // Check that we don't have a SSL filter already present in the chain + if (parent.contains(SSL2Filter.class)) { + String msg = "Only one SSL filter is permitted in a chain."; + LOGGER.error(msg); + throw new IllegalStateException(msg); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Adding the SSL Filter {} to the chain", name); + } + } + + @Override + public void onPreRemove(IoFilterChain parent, String name, NextFilter next) throws Exception { + IoSession session = parent.getSession(); + session.removeAttribute(SSL_HANDLER); + } + + @Override + public void sessionOpened(NextFilter next, IoSession session) throws Exception { + + LOGGER.debug("session openend {}", session); + + StackInspector.get().printStackTrace(); + + SSL2Handler x = SSL2Handler.class.cast(session.getAttribute(SSL_HANDLER)); + + if (x == null) { + SSLEngine e = mContext.createSSLEngine(); + + e.setNeedClientAuth(mNeedClientAuth); + e.setWantClientAuth(mWantClientAuth); + e.setEnabledCipherSuites(mEnabledCipherSuites); + e.setEnabledProtocols(mEnabledProtocols); + e.setUseClientMode(!session.isServer()); + + x = new SSL2HandlerG0(e, EXECUTOR, session); + + session.setAttribute(SSL_HANDLER, x); + } + + x.open(next); + } + + @Override + public void messageReceived(NextFilter next, IoSession session, Object message) throws Exception { + SSL2Handler x = SSL2Handler.class.cast(session.getAttribute(SSL_HANDLER)); + x.receive(next, IoBuffer.class.cast(message)); + } + + @Override + public void messageSent(NextFilter next, IoSession session, WriteRequest writeRequest) throws Exception { + if (writeRequest instanceof EncryptedWriteRequest) { + EncryptedWriteRequest e = EncryptedWriteRequest.class.cast(writeRequest); + SSL2Handler x = SSL2Handler.class.cast(session.getAttribute(SSL_HANDLER)); + x.ack(next, writeRequest); + if (e.getParentRequest() != null) { + next.messageSent(session, e.getParentRequest()); + } + } else { + super.messageSent(next, session, writeRequest); + } + } + + @Override + public void filterWrite(NextFilter next, IoSession session, WriteRequest writeRequest) throws Exception { + if (writeRequest instanceof EncryptedWriteRequest) { + super.filterWrite(next, session, writeRequest); + } else { + SSL2Handler x = SSL2Handler.class.cast(session.getAttribute(SSL_HANDLER)); + x.write(next, writeRequest); + } + } +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl2/SSL2Handler.java b/mina-core/src/main/java/org/apache/mina/filter/ssl2/SSL2Handler.java new file mode 100644 index 0000000..601e73b --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl2/SSL2Handler.java @@ -0,0 +1,217 @@ +package org.apache.mina.filter.ssl2; + +import java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.Executor; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.filterchain.IoFilter.NextFilter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.write.WriteRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class SSL2Handler { + + /** + * Static logger + */ + static protected final Logger LOGGER = LoggerFactory.getLogger(SSL2Handler.class); + + /** + * Write Requests which are enqueued prior to the completion of the handshaking + */ + protected final Deque<WriteRequest> mWriteQueue = new ConcurrentLinkedDeque<>(); + + /** + * Requests which have been sent to the socket and waiting acknowledgment + */ + protected final Deque<WriteRequest> mAckQueue = new ConcurrentLinkedDeque<>(); + + /** + * SSL Engine + */ + protected final SSLEngine mEngine; + + /** + * Task executor + */ + protected final Executor mExecutor; + + /** + * Socket session + */ + protected final IoSession mSession; + + /** + * Progressive decoder buffer + */ + protected IoBuffer mReceiveBuffer; + + public SSL2Handler(SSLEngine p, Executor e, IoSession s) { + this.mEngine = p; + this.mExecutor = e; + this.mSession = s; + } + + /** + * Opens the encryption session, this may include sending the initial handshake + * message + * + * @param session + * @param next + * + * @throws SSLException + */ + abstract public void open(NextFilter next) throws SSLException; + + /** + * Decodes encrypted messages and passes the results to the {@code next} filter. + * + * @param message + * @param session + * @param next + * + * @throws SSLException + */ + abstract public void receive(NextFilter next, final IoBuffer message) throws SSLException; + + /** + * Acknowledge that a {@link WriteRequest} has been successfully written to the + * {@link IoSession} + * <p> + * This functionality is used to enforce flow control by allowing only a + * specific number of pending write operations at any moment of time. When one + * {@code WriteRequest} is acknowledged, another can be encoded and written. + * + * @param request + * @param session + * @param next + * + * @throws SSLException + */ + abstract public void ack(NextFilter next, final WriteRequest request) throws SSLException; + + /** + * Encrypts and writes the specified {@link WriteRequest} to the + * {@link IoSession} or enqueues it to be processed later. + * <p> + * The encryption session may be currently handshaking preventing application + * messages from being written. + * + * @param request + * @param session + * @param next + * + * @throws SSLException + */ + abstract public void write(NextFilter next, final WriteRequest request) throws SSLException; + + /** + * Closes the encryption session and writes any required messages + * + * @param session + * @param next + * + * @throws SSLException + */ + abstract public void close(NextFilter next) throws SSLException; + + /** + * {@inheritDoc} + */ + public String toString() { + StringBuilder b = new StringBuilder(); + + b.append(this.getClass().getSimpleName()); + b.append("@"); + b.append(Integer.toHexString(this.hashCode())); + b.append("[mode="); + + if (this.mEngine.getUseClientMode()) { + b.append("client"); + } else { + b.append("server"); + } + + b.append("]"); + + return b.toString(); + } + + /** + * Combines the received data with any previously received data + * + * @param source received data + * @return buffer to decode + */ + protected IoBuffer resume_decode_buffer(IoBuffer source) { + if (mReceiveBuffer == null) + if (source == null) + return IoBuffer.allocate(0); + else + return source; + else { + if (source != null) { + mReceiveBuffer.expand(source.remaining()); + mReceiveBuffer.put(source); + source.free(); + } + mReceiveBuffer.flip(); + return mReceiveBuffer; + } + } + + /** + * Stores data for later use if any is remaining + * + * @param source the buffer previously returned by + * {@link #resume_decode_buffer(IoBuffer)} + */ + protected void save_decode_buffer(IoBuffer source) { + if (source.hasRemaining()) { + if (source.isDerived()) { + this.mReceiveBuffer = IoBuffer.allocate(source.remaining()); + this.mReceiveBuffer.put(source); + } else { + source.compact(); + this.mReceiveBuffer = source; + } + } else { + source.free(); + this.mReceiveBuffer = null; + } + } + + /** + * Allocates the default encoder buffer for the given source size + * + * @param source + * @return buffer + */ + protected IoBuffer allocate_encode_buffer(int estimate) { + SSLSession session = this.mEngine.getHandshakeSession(); + if (session == null) + session = this.mEngine.getSession(); + int packets = Math.max(2, Math.min(16, 1 + (estimate / session.getApplicationBufferSize()))); + return IoBuffer.allocate(packets * session.getPacketBufferSize()); + } + + /** + * Allocates the default decoder buffer for the given source size + * + * @param source + * @return buffer + */ + protected IoBuffer allocate_app_buffer(int estimate) { + SSLSession session = this.mEngine.getHandshakeSession(); + if (session == null) + session = this.mEngine.getSession(); + int packets = 1 + (estimate / session.getPacketBufferSize()); + return IoBuffer.allocate(packets * session.getApplicationBufferSize()); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl2/SSL2HandlerG0.java b/mina-core/src/main/java/org/apache/mina/filter/ssl2/SSL2HandlerG0.java new file mode 100644 index 0000000..700ebdd --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl2/SSL2HandlerG0.java @@ -0,0 +1,318 @@ +package org.apache.mina.filter.ssl2; + +import java.util.concurrent.Executor; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.filterchain.IoFilter.NextFilter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.write.WriteRequest; + +public class SSL2HandlerG0 extends SSL2Handler { + + public SSL2HandlerG0(SSLEngine p, Executor e, IoSession s) { + super(p, e, s); + } + + synchronized public void open(final NextFilter next) throws SSLException { + if (this.mEngine.getUseClientMode()) { + this.mEngine.beginHandshake(); + this.lwrite(next); + } + } + + synchronized public void receive(final NextFilter next, final IoBuffer message) throws SSLException { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive() - source {}", toString(), message); + } + + final IoBuffer input = resume_decode_buffer(message); + + try { + while (lreceive(next, input) && message.hasRemaining()) { + // spin + } + } finally { + save_decode_buffer(input); + } + } + + /** + * Process a received message + * + * @param message received data + * @param session user session + * @param next filter + * @return {@code true} if some of the message was consumed + * @throws SSLException + */ + @SuppressWarnings("incomplete-switch") + protected boolean lreceive(final NextFilter next, final IoBuffer message) throws SSLException { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lreceive() - source {}", toString(), message); + } + + final IoBuffer source = message == null ? IoBuffer.allocate(0) : message; + final IoBuffer dest = allocate_app_buffer(source.remaining()); + + final SSLEngineResult result = mEngine.unwrap(source.buf(), dest.buf()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lreceive() - bytes-consumed {}, bytes-produced {}, status {}", toString(), + result.bytesConsumed(), result.bytesProduced(), result.getStatus()); + } + + if (result.bytesProduced() == 0) { + dest.free(); + } else { + dest.flip(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lreceive() - result {}", toString(), dest); + } + + next.messageReceived(this.mSession, dest); + } + + switch (result.getHandshakeStatus()) { + case NEED_TASK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lreceive() - handshake needs task, scheduling tasks", toString()); + } + this.schedule_task(next); + break; + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lreceive() - handshake needs to write a new message", toString()); + } + this.lwrite(next); + break; + case FINISHED: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lreceive() - handshake finished, flushing pending requests", toString()); + } + this.lflush(next); + break; + } + + return result.bytesConsumed() > 0; + } + + synchronized public void ack(final NextFilter next, final WriteRequest request) throws SSLException { + + } + + synchronized public void write(final NextFilter next, final WriteRequest request) throws SSLException { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write() - source {}", toString(), request); + } + + this.mWriteQueue.add(request); + this.lflush(next); + } + + /** + * Attempts to encode the WriteRequest and write the data to the IoSession + * + * @param request + * @param session + * @param next + * @return {@code true} if the WriteRequest was successfully written + * @throws SSLException + */ + @SuppressWarnings("incomplete-switch") + synchronized protected boolean lwrite(final NextFilter next, final WriteRequest request) throws SSLException { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - source {}", toString(), request); + } + + final IoBuffer source = IoBuffer.class.cast(request.getMessage()); + final IoBuffer dest = allocate_encode_buffer(source.remaining()); + + final SSLEngineResult result = this.mEngine.wrap(source.buf(), dest.buf()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - bytes-consumed {}, bytes-produced {}, status {}, handshake {}", toString(), + result.bytesConsumed(), result.bytesProduced(), result.getStatus(), result.getHandshakeStatus()); + } + + if (result.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) { + // then we probably consumed some data + dest.flip(); + if (source.hasRemaining()) { + next.filterWrite(this.mSession, new EncryptedWriteRequest(dest, null)); + lwrite(next, request); // write additional chunks + } else { + source.rewind(); + next.filterWrite(this.mSession, new EncryptedWriteRequest(dest, request)); + } + + return true; + } else { + if (dest.position() == 0) { + dest.free(); + } else { + next.filterWrite(this.mSession, new EncryptedWriteRequest(dest, null)); + } + + switch (result.getHandshakeStatus()) { + case NEED_TASK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - handshake needs task, scheduling tasks", toString()); + } + this.schedule_task(next); + break; + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - handshake needs to encode a message", toString()); + } + return this.lwrite(next, request); + case FINISHED: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - handshake finished, flushing pending requests", toString()); + } + if (this.lwrite(next, request)) { + this.lflush(next); + return true; + } + break; + } + } + + return false; + } + + /** + * Attempts to generate a handshake message and write the data to the IoSession + * + * @param session + * @param next + * @return {@code true} if a message was generated and written + * @throws SSLException + */ + @SuppressWarnings("incomplete-switch") + synchronized protected boolean lwrite(NextFilter next) throws SSLException { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - internal", toString()); + } + + final IoBuffer source = IoBuffer.allocate(0); + final IoBuffer dest = allocate_encode_buffer(source.remaining()); + + final SSLEngineResult result = this.mEngine.wrap(source.buf(), dest.buf()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - bytes-consumed {}, bytes-produced {}", toString(), result.bytesConsumed(), + result.bytesProduced()); + } + + if (dest.position() == 0) { + dest.free(); + } else { + dest.flip(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - result {}", toString(), dest); + } + + final EncryptedWriteRequest encrypted = new EncryptedWriteRequest(dest, null); + next.filterWrite(this.mSession, encrypted); + } + + switch (result.getHandshakeStatus()) { + case NEED_TASK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - handshake needs task, scheduling tasks", toString()); + } + this.schedule_task(next); + break; + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - handshake needs to encode a message", toString()); + } + this.lwrite(next); + break; + case FINISHED: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} lwrite() - handshake finished, flushing pending requests", toString()); + } + this.lflush(next); + break; + } + + return result.bytesProduced() > 0; + } + + protected void lflush(final NextFilter next) throws SSLException { + if (this.mWriteQueue.isEmpty()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} flush() - no saved messages", toString()); + } + return; + } + + WriteRequest current = null; + + while ((current = this.mWriteQueue.poll()) != null) { + if (lwrite(next, current) == false) { + this.mWriteQueue.addFirst(current); + break; + } + } + } + + synchronized public void close(final NextFilter next) throws SSLException { + if (mEngine.isOutboundDone()) + return; + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} close() - closing session", toString()); + } + + mEngine.closeOutbound(); + this.lwrite(next); + } + + protected void schedule_task(final NextFilter next) { + if (this.mExecutor == null) { + this.execute_task(next); + } else { + this.mExecutor.execute(new Runnable() { + @Override + public void run() { + SSL2HandlerG0.this.execute_task(next); + } + }); + } + } + + synchronized protected void execute_task(final NextFilter next) { + Runnable t = null; + while ((t = mEngine.getDelegatedTask()) != null) { + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} task() - executing {}", toString(), t); + } + + t.run(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} task() - writing handshake messages", toString()); + } + + lwrite(next); + } catch (SSLException e) { + e.printStackTrace(); + } + } + } +} diff --git a/mina-core/src/main/java/org/apache/mina/util/BasicThreadFactory.java b/mina-core/src/main/java/org/apache/mina/util/BasicThreadFactory.java new file mode 100644 index 0000000..7e73017 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/util/BasicThreadFactory.java @@ -0,0 +1,62 @@ +/* + * 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.util; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Utility for creating thread factories + * + * @author <a href="http://mina.apache.org">Apache MINA Project</a> + * @author Jonathan Valliere + */ +public class BasicThreadFactory implements java.util.concurrent.ThreadFactory { + public final AtomicInteger count = new AtomicInteger(0); + public final String name; + + public final boolean deamon; + public final int priority; + + public BasicThreadFactory(String basename, boolean daemon, int priority) { + this.name = basename; + this.deamon = daemon; + this.priority = priority; + } + + public BasicThreadFactory(String basename, boolean daemon) { + this(basename, daemon, Thread.NORM_PRIORITY); + } + + public BasicThreadFactory(String basename) { + this(basename, false, Thread.NORM_PRIORITY); + } + + @Override + public Thread newThread(Runnable pool) { + Thread t = new Thread(pool); + + t.setName(this.name + "-" + this.count.getAndIncrement()); + t.setPriority(this.priority); + t.setDaemon(this.deamon); + + return t; + } + +} diff --git a/mina-core/src/main/java/org/apache/mina/util/StackInspector.java b/mina-core/src/main/java/org/apache/mina/util/StackInspector.java new file mode 100644 index 0000000..45b3414 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/util/StackInspector.java @@ -0,0 +1,78 @@ +/* + * 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.util; + +/** + * Utility to retrieving the thread stack debug information + * + * @author <a href="http://mina.apache.org">Apache MINA Project</a> + * @author Jonathan Valliere + */ +public class StackInspector extends RuntimeException { + static public final StackTraceElement callee() { + return Thread.currentThread().getStackTrace()[3]; + } + + static public final StackInspector get(String message) { + try { + throw new StackInspector(message); + } catch (StackInspector e0) { + return e0; + } + } + + static public final StackInspector get(Throwable cause) { + try { + throw new StackInspector(cause); + } catch (StackInspector e0) { + return e0; + } + } + + static public final StackInspector get() { + try { + throw new StackInspector("Stack from Thread: " + Thread.currentThread().getName()); + } catch (StackInspector e0) { + return e0; + } + } + + static private final long serialVersionUID = 1L; + + StackInspector() { + + } + + StackInspector(String message) { + super(message); + } + + StackInspector(Throwable cause) { + super(cause); + } + + StackInspector(String message, Throwable cause) { + super(message, cause); + } + + StackInspector(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/mina-core/src/test/java/org/apache/mina/filter/ssl2/SSL2SimpleTest.java b/mina-core/src/test/java/org/apache/mina/filter/ssl2/SSL2SimpleTest.java new file mode 100644 index 0000000..d198459 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/filter/ssl2/SSL2SimpleTest.java @@ -0,0 +1,114 @@ +package org.apache.mina.filter.ssl2; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +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.core.write.DefaultWriteRequest; +import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.ssl.SslDIRMINA937Test; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import org.apache.mina.transport.socket.nio.NioSocketConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SSL2SimpleTest { + + public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, + UnrecoverableKeyException, CertificateException, IOException { + // System.setProperty("javax.net.debug", "all"); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + + KeyStore ks = KeyStore.getInstance("JKS"); + KeyStore ts = KeyStore.getInstance("JKS"); + + ks.load(SslDIRMINA937Test.class.getResourceAsStream("keystore.sslTest"), "password".toCharArray()); + ts.load(SslDIRMINA937Test.class.getResourceAsStream("truststore.sslTest"), "password".toCharArray()); + + kmf.init(ks, "password".toCharArray()); + tmf.init(ts); + + final SSLContext context = SSLContext.getInstance("TLS"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); + + final SSL2Filter filter = new SSL2Filter(context); + filter.setEnabledCipherSuites(new String[] { "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384" }); + filter.setEnabledProtocols(new String[] { "TLSv1.3" }); + + final IoAcceptor socket_acceptor = new NioSocketAcceptor(); + + socket_acceptor.getFilterChain().addFirst("ssl", filter); + socket_acceptor.setHandler(new DebugFilter()); + + final IoConnector socket_connector = new NioSocketConnector(); + + socket_connector.getFilterChain().addFirst("ssl", filter); + socket_connector.setHandler(new DebugFilter()); + + final InetSocketAddress server_address = new InetSocketAddress("0.0.0.0", 53301); + socket_acceptor.bind(server_address); + + final IoFuture connect_future = socket_connector.connect(server_address); + connect_future.awaitUninterruptibly(); + + final IoSession client_socket = connect_future.getSession(); + + client_socket.write(createWriteRequest()).awaitUninterruptibly(); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + + } + + client_socket.closeOnFlush().awaitUninterruptibly(); + + socket_connector.dispose(); + + socket_acceptor.unbind(); + socket_acceptor.dispose(); + } + + public static class DebugFilter extends IoHandlerAdapter { + protected static final Logger LOGGER = LoggerFactory.getLogger(DebugFilter.class); + + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + + IoBuffer b = IoBuffer.class.cast(message); + LOGGER.debug("received clear-text message\n" + b.getHexDump(true)); + } + } + + public static WriteRequest createWriteRequest() { + // HTTP request + StringBuilder http = new StringBuilder(); + http.append("GET / HTTP/1.0\r\n"); + http.append("Connection: close\r\n"); + http.append("\r\n"); + + IoBuffer message = IoBuffer.allocate(1024); + message.put(http.toString().getBytes()); + message.flip(); + + return new DefaultWriteRequest(message); + } +} diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl2/keystore.sslTest b/mina-core/src/test/resources/org/apache/mina/filter/ssl2/keystore.sslTest new file mode 100644 index 0000000..36190ba Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl2/keystore.sslTest differ diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl2/truststore.sslTest b/mina-core/src/test/resources/org/apache/mina/filter/ssl2/truststore.sslTest new file mode 100644 index 0000000..48c5963 Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl2/truststore.sslTest differ