blob: 52ce853812e437a89245f2ef123f48748ef0011c [file] [log] [blame]
/*
* 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.util.ArrayList;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.IoFilter;
import org.apache.mina.core.filterchain.IoFilterAdapter;
import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.future.DefaultWriteFuture;
import org.apache.mina.core.future.IoFuture;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandler;
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.core.write.WriteRequestWrapper;
import org.apache.mina.core.write.WriteToClosedSessionException;
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.
*
* <h2>Implementing StartTLS</h2>
* <p>
* You can use {@link #DISABLE_ENCRYPTION_ONCE} attribute to implement StartTLS:
* <pre>
* public void messageReceived(IoSession session, Object message) {
* if (message instanceof MyStartTLSRequest) {
* // Insert SSLFilter to get ready for handshaking
* session.getFilterChain().addFirst(sslFilter);
*
* // Disable encryption temporarily.
* // This attribute will be removed by SSLFilter
* // inside the Session.write() call below.
* session.setAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE);
*
* // Write StartTLSResponse which won't be encrypted.
* session.write(new MyStartTLSResponse(OK));
*
* // Now DISABLE_ENCRYPTION_ONCE attribute is cleared.
* assert session.getAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE) == null;
* }
* }
* </pre>
*
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
* @org.apache.xbean.XBean
*/
public class SslFilter extends IoFilterAdapter {
/** The logger */
private static final Logger LOGGER = LoggerFactory.getLogger(SslFilter.class);
/**
* A session attribute key that stores underlying {@link SSLSession}
* for each session.
*/
public static final AttributeKey SSL_SESSION = new AttributeKey(SslFilter.class, "session");
/**
* A session attribute key that makes next one write request bypass
* this filter (not encrypting the data). This is a marker attribute,
* which means that you can put whatever as its value. ({@link Boolean#TRUE}
* is preferred.) The attribute is automatically removed from the session
* attribute map as soon as {@link IoSession#write(Object)} is invoked,
* and therefore should be put again if you want to make more messages
* bypass this filter. This is especially useful when you implement
* StartTLS.
*/
public static final AttributeKey DISABLE_ENCRYPTION_ONCE = new AttributeKey(SslFilter.class, "disableOnce");
/**
* A session attribute key that makes this filter to emit a
* {@link IoHandler#messageReceived(IoSession, Object)} event with a
* special message ({@link #SESSION_SECURED} or {@link #SESSION_UNSECURED}).
* This is a marker attribute, which means that you can put whatever as its
* value. ({@link Boolean#TRUE} is preferred.) By default, this filter
* doesn't emit any events related with SSL session flow control.
*/
public static final AttributeKey USE_NOTIFICATION = new AttributeKey(SslFilter.class, "useNotification");
/**
* 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 PEER_ADDRESS = new AttributeKey(SslFilter.class, "peerAddress");
/**
* A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)}
* event when the session is secured and its {@link #USE_NOTIFICATION}
* attribute is set.
*/
public static final SslFilterMessage SESSION_SECURED = new SslFilterMessage("SESSION_SECURED");
/**
* A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)}
* event when the session is not secure anymore and its {@link #USE_NOTIFICATION}
* attribute is set.
*/
public static final SslFilterMessage SESSION_UNSECURED = new SslFilterMessage("SESSION_UNSECURED");
/** An attribute containing the next filter */
private static final AttributeKey NEXT_FILTER = new AttributeKey(SslFilter.class, "nextFilter");
private static final AttributeKey SSL_HANDLER = new AttributeKey(SslFilter.class, "handler");
/** The SslContext used */
/* No qualifier */final SSLContext sslContext;
/** A flag used to tell the filter to start the handshake immediately */
private final boolean autoStart;
/** A flag used to determinate if the handshake should start immediately */
public static final boolean START_HANDSHAKE = true;
/** A flag used to determinate if the handshake should wait for the client to initiate the handshake */
public static final boolean CLIENT_HANDSHAKE = false;
private boolean client;
private boolean needClientAuth;
private boolean wantClientAuth;
private String[] enabledCipherSuites;
private String[] enabledProtocols;
/**
* Creates a new SSL filter using the specified {@link SSLContext}.
* The handshake will start immediately after the filter has been added
* to the chain.
*
* @param sslContext The SSLContext to use
*/
public SslFilter(SSLContext sslContext) {
this(sslContext, START_HANDSHAKE);
}
/**
* Creates a new SSL filter using the specified {@link SSLContext}.
* If the <tt>autostart</tt> flag is set to <tt>true</tt>, the
* handshake will start immediately after the filter has been added
* to the chain.
*
* @param sslContext The SSLContext to use
* @param autoStart The flag used to tell the filter to start the handshake immediately
*/
public SslFilter(SSLContext sslContext, boolean autoStart) {
if (sslContext == null) {
throw new IllegalArgumentException("sslContext");
}
this.sslContext = sslContext;
this.autoStart = autoStart;
}
/**
* Returns the underlying {@link SSLSession} for the specified session.
*
* @param session The current session
* @return <tt>null</tt> if no {@link SSLSession} is initialized yet.
*/
public SSLSession getSslSession(IoSession session) {
return (SSLSession) session.getAttribute(SSL_SESSION);
}
/**
* (Re)starts SSL session for the specified <tt>session</tt> if not started yet.
* Please note that SSL session is automatically started by default, and therefore
* you don't need to call this method unless you've used TLS closure.
*
* @param session The session that will be switched to SSL mode
* @return <tt>true</tt> if the SSL session has been started, <tt>false</tt> if already started.
* @throws SSLException if failed to start the SSL session
*/
public boolean startSsl(IoSession session) throws SSLException {
SslHandler sslHandler = getSslSessionHandler(session);
boolean started;
try {
synchronized (sslHandler) {
if (sslHandler.isOutboundDone()) {
NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER);
sslHandler.destroy();
sslHandler.init();
sslHandler.handshake(nextFilter);
started = true;
} else {
started = false;
}
}
sslHandler.flushScheduledEvents();
} catch (SSLException se) {
sslHandler.release();
throw se;
}
return started;
}
/**
* An extended toString() method for sessions. If the SSL handshake
* is not yet completed, we will print (ssl) in small caps. Once it's
* completed, we will use SSL capitalized.
*/
/* no qualifier */String getSessionInfo(IoSession session) {
StringBuilder sb = new StringBuilder();
if (session.getService() instanceof IoAcceptor) {
sb.append("Session Server");
} else {
sb.append("Session Client");
}
sb.append('[').append(session.getId()).append(']');
SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
if (sslHandler == null) {
sb.append("(no sslEngine)");
} else if (isSslStarted(session)) {
if (sslHandler.isHandshakeComplete()) {
sb.append("(SSL)");
} else {
sb.append("(ssl...)");
}
}
return sb.toString();
}
/**
* @return <tt>true</tt> if and only if the specified <tt>session</tt> is
* encrypted/decrypted over SSL/TLS currently. This method will start
* to return <tt>false</tt> after TLS <tt>close_notify</tt> message
* is sent and any messages written after then is not going to get encrypted.
*
* @param session the session we want to check
*/
public boolean isSslStarted(IoSession session) {
SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
if (sslHandler == null) {
return false;
}
synchronized (sslHandler) {
return !sslHandler.isOutboundDone();
}
}
/**
* @return <tt>true</tt> if and only if the conditions for
* {@link #isSslStarted(IoSession)} are met, and the handhake has
* completed.
*
* @param session the session we want to check
*/
public boolean isSecured(IoSession session) {
SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
if (sslHandler == null) {
return false;
}
synchronized (sslHandler) {
return !sslHandler.isOutboundDone() && sslHandler.isHandshakeComplete();
}
}
/**
* Stops the SSL session by sending TLS <tt>close_notify</tt> message to
* initiate TLS closure.
*
* @param session the {@link IoSession} to initiate TLS closure
* @return The Future for the initiated closure
* @throws SSLException if failed to initiate TLS closure
*/
public WriteFuture stopSsl(IoSession session) throws SSLException {
SslHandler sslHandler = getSslSessionHandler(session);
NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER);
WriteFuture future;
try {
synchronized (sslHandler) {
future = initiateClosure(nextFilter, session);
}
sslHandler.flushScheduledEvents();
} catch (SSLException se) {
sslHandler.release();
throw se;
}
return future;
}
/**
* @return <tt>true</tt> if the engine is set to use client mode
* when handshaking.
*/
public boolean isUseClientMode() {
return client;
}
/**
* Configures the engine to use client (or server) mode when handshaking.
*
* @param clientMode <tt>true</tt> when we are in client mode, <tt>false</tt> when in server mode
*/
public void setUseClientMode(boolean clientMode) {
this.client = clientMode;
}
/**
* @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 needClientAuth;
}
/**
* 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.needClientAuth = 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 wantClientAuth;
}
/**
* 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.wantClientAuth = 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 enabledCipherSuites;
}
/**
* 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.enabledCipherSuites = 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 enabledProtocols;
}
/**
* 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.enabledProtocols = 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 nextFilter) throws SSLException {
// Check that we don't have a SSL filter already present in the chain
if (parent.contains(SslFilter.class)) {
String msg = "Only one SSL filter is permitted in a chain.";
LOGGER.error(msg);
throw new IllegalStateException(msg);
}
LOGGER.debug("Adding the SSL Filter {} to the chain", name);
IoSession session = parent.getSession();
session.setAttribute(NEXT_FILTER, nextFilter);
// Create a SSL handler and start handshake.
SslHandler sslHandler = new SslHandler(this, session);
// Adding the supported ciphers in the SSLHandler
if ((enabledCipherSuites == null) || (enabledCipherSuites.length == 0)) {
enabledCipherSuites = sslContext.getServerSocketFactory().getSupportedCipherSuites();
}
sslHandler.init();
session.setAttribute(SSL_HANDLER, sslHandler);
}
@Override
public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
if (autoStart == START_HANDSHAKE) {
initiateHandshake(nextFilter, parent.getSession());
}
}
@Override
public void onPreRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
IoSession session = parent.getSession();
stopSsl(session);
session.removeAttribute(NEXT_FILTER);
session.removeAttribute(SSL_HANDLER);
}
// IoFilter impl.
@Override
public void sessionClosed(NextFilter nextFilter, IoSession session) throws SSLException {
SslHandler sslHandler = getSslSessionHandler(session);
try {
synchronized (sslHandler) {
// release resources
sslHandler.destroy();
}
} finally {
// notify closed session
nextFilter.sessionClosed(session);
}
}
@Override
public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws SSLException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("{}: Message received : {}", getSessionInfo(session), message);
}
SslHandler sslHandler = getSslSessionHandler(session);
synchronized (sslHandler) {
if (!isSslStarted(session) && sslHandler.isInboundDone()) {
// The SSL session must be established first before we
// can push data to the application. Store the incoming
// data into a queue for a later processing
sslHandler.scheduleMessageReceived(nextFilter, message);
} else {
IoBuffer buf = (IoBuffer) message;
try {
if (sslHandler.isOutboundDone()) {
sslHandler.destroy();
throw new SSLException("Outbound done");
}
// forward read encrypted data to SSL handler
sslHandler.messageReceived(nextFilter, buf.buf());
// Handle data to be forwarded to application or written to net
handleSslData(nextFilter, sslHandler);
if (sslHandler.isInboundDone()) {
if (sslHandler.isOutboundDone()) {
sslHandler.destroy();
} else {
initiateClosure(nextFilter, session);
}
if (buf.hasRemaining()) {
// Forward the data received after closure.
sslHandler.scheduleMessageReceived(nextFilter, buf);
}
}
} catch (SSLException ssle) {
if (!sslHandler.isHandshakeComplete()) {
SSLException newSsle = new SSLHandshakeException("SSL handshake failed.");
newSsle.initCause(ssle);
ssle = newSsle;
// Close the session immediately, the handshake has failed
session.closeNow();
} else {
// Free the SSL Handler buffers
sslHandler.release();
}
throw ssle;
}
}
}
sslHandler.flushScheduledEvents();
}
@Override
public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) {
if (writeRequest instanceof EncryptedWriteRequest) {
EncryptedWriteRequest wrappedRequest = (EncryptedWriteRequest) writeRequest;
nextFilter.messageSent(session, wrappedRequest.getParentRequest());
} else {
// ignore extra buffers used for handshaking
}
}
@Override
public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception {
if (cause instanceof WriteToClosedSessionException) {
// Filter out SSL close notify, which is likely to fail to flush
// due to disconnection.
WriteToClosedSessionException e = (WriteToClosedSessionException) cause;
List<WriteRequest> failedRequests = e.getRequests();
boolean containsCloseNotify = false;
for (WriteRequest r : failedRequests) {
if (isCloseNotify(r.getMessage())) {
containsCloseNotify = true;
break;
}
}
if (containsCloseNotify) {
if (failedRequests.size() == 1) {
// close notify is the only failed request; bail out.
return;
}
List<WriteRequest> newFailedRequests = new ArrayList<>(failedRequests.size() - 1);
for (WriteRequest r : failedRequests) {
if (!isCloseNotify(r.getMessage())) {
newFailedRequests.add(r);
}
}
if (newFailedRequests.isEmpty()) {
// the failedRequests were full with close notify; bail out.
return;
}
cause = new WriteToClosedSessionException(newFailedRequests, cause.getMessage(), cause.getCause());
}
}
nextFilter.exceptionCaught(session, cause);
}
private boolean isCloseNotify(Object message) {
if (!(message instanceof IoBuffer)) {
return false;
}
IoBuffer buf = (IoBuffer) message;
int offset = buf.position();
return (buf.get(offset + 0) == 0x15) /* Alert */
&& (buf.get(offset + 1) == 0x03) /* TLS/SSL */
&& ((buf.get(offset + 2) == 0x00) /* SSL 3.0 */
|| (buf.get(offset + 2) == 0x01) /* TLS 1.0 */
|| (buf.get(offset + 2) == 0x02) /* TLS 1.1 */
|| (buf.get(offset + 2) == 0x03)) /* TLS 1.2 */
&& (buf.get(offset + 3) == 0x00); /* close_notify */
}
@Override
public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws SSLException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("{}: Writing Message : {}", getSessionInfo(session), writeRequest);
}
boolean needsFlush = true;
SslHandler sslHandler = getSslSessionHandler(session);
try {
synchronized (sslHandler) {
if (!isSslStarted(session)) {
sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
}
// Don't encrypt the data if encryption is disabled.
else if (session.containsAttribute(DISABLE_ENCRYPTION_ONCE)) {
// Remove the marker attribute because it is temporary.
session.removeAttribute(DISABLE_ENCRYPTION_ONCE);
sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
} else {
// Otherwise, encrypt the buffer.
IoBuffer buf = (IoBuffer) writeRequest.getMessage();
if (sslHandler.isWritingEncryptedData()) {
// data already encrypted; simply return buffer
sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
} else if (sslHandler.isHandshakeComplete()) {
// SSL encrypt
buf.mark();
sslHandler.encrypt(buf.buf());
IoBuffer encryptedBuffer = sslHandler.fetchOutNetBuffer();
sslHandler.scheduleFilterWrite(nextFilter, new EncryptedWriteRequest(writeRequest,
encryptedBuffer));
} else {
if (session.isConnected()) {
// Handshake not complete yet.
sslHandler.schedulePreHandshakeWriteRequest(nextFilter, writeRequest);
}
needsFlush = false;
}
}
}
if (needsFlush) {
sslHandler.flushScheduledEvents();
}
} catch (SSLException se) {
sslHandler.release();
throw se;
}
}
@Override
public void filterClose(final NextFilter nextFilter, final IoSession session) throws SSLException {
SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
if (sslHandler == null) {
// The connection might already have closed, or
// SSL might have not started yet.
nextFilter.filterClose(session);
return;
}
WriteFuture future = null;
try {
synchronized (sslHandler) {
if (isSslStarted(session)) {
future = initiateClosure(nextFilter, session);
future.addListener(new IoFutureListener<IoFuture>() {
@Override
public void operationComplete(IoFuture future) {
nextFilter.filterClose(session);
}
});
}
}
sslHandler.flushScheduledEvents();
} catch (SSLException se) {
sslHandler.release();
throw se;
} finally {
if (future == null) {
nextFilter.filterClose(session);
}
}
}
/**
* Initiate the SSL handshake. This can be invoked if you have set the 'autoStart' to
* false when creating the SslFilter instance.
*
* @param session The session for which the SSL handshake should be done
* @throws SSLException If the handshake failed
*/
public void initiateHandshake(IoSession session) throws SSLException {
IoFilterChain filterChain = session.getFilterChain();
if (filterChain == null) {
throw new SSLException("No filter chain");
}
IoFilter.NextFilter nextFilter = filterChain.getNextFilter(SslFilter.class);
if (nextFilter == null) {
throw new SSLException("No SSL next filter in the chain");
}
initiateHandshake(nextFilter, session);
}
private void initiateHandshake(NextFilter nextFilter, IoSession session) throws SSLException {
LOGGER.debug("{} : Starting the first handshake", getSessionInfo(session));
SslHandler sslHandler = getSslSessionHandler(session);
try {
synchronized (sslHandler) {
sslHandler.handshake(nextFilter);
}
sslHandler.flushScheduledEvents();
} catch (SSLException se) {
sslHandler.release();
throw se;
}
}
private WriteFuture initiateClosure(NextFilter nextFilter, IoSession session) throws SSLException {
SslHandler sslHandler = getSslSessionHandler(session);
WriteFuture future = null;
// if already shut down
try {
synchronized(sslHandler) {
if (!sslHandler.closeOutbound()) {
return DefaultWriteFuture.newNotWrittenFuture(session, new IllegalStateException(
"SSL session is shut down already."));
}
// there might be data to write out here?
future = sslHandler.writeNetBuffer(nextFilter);
if (future == null) {
future = DefaultWriteFuture.newWrittenFuture(session);
}
if (sslHandler.isInboundDone()) {
sslHandler.destroy();
}
}
if (session.containsAttribute(USE_NOTIFICATION)) {
sslHandler.scheduleMessageReceived(nextFilter, SESSION_UNSECURED);
}
} catch (SSLException se) {
sslHandler.release();
throw se;
}
return future;
}
// Utilities
private void handleSslData(NextFilter nextFilter, SslHandler sslHandler) throws SSLException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("{}: Processing the SSL Data ", getSessionInfo(sslHandler.getSession()));
}
// Flush any buffered write requests occurred before handshaking.
if (sslHandler.isHandshakeComplete()) {
sslHandler.flushPreHandshakeEvents();
}
// Write encrypted data to be written (if any)
sslHandler.writeNetBuffer(nextFilter);
// handle app. data read (if any)
handleAppDataRead(nextFilter, sslHandler);
}
private void handleAppDataRead(NextFilter nextFilter, SslHandler sslHandler) {
// forward read app data
IoBuffer readBuffer = sslHandler.fetchAppBuffer();
if (readBuffer.hasRemaining()) {
sslHandler.scheduleMessageReceived(nextFilter, readBuffer);
}
}
private SslHandler getSslSessionHandler(IoSession session) {
SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
if (sslHandler == null) {
throw new IllegalStateException();
}
synchronized(sslHandler) {
if (sslHandler.getSslFilter() != this) {
throw new IllegalArgumentException("Not managed by this filter.");
}
}
return sslHandler;
}
/**
* A message that is sent from {@link SslFilter} when the connection became
* secure or is not secure anymore.
*
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
*/
public static class SslFilterMessage {
private final String name;
private SslFilterMessage(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
private static class EncryptedWriteRequest extends WriteRequestWrapper {
private final IoBuffer encryptedMessage;
private EncryptedWriteRequest(WriteRequest writeRequest, IoBuffer encryptedMessage) {
super(writeRequest);
this.encryptedMessage = encryptedMessage;
}
@Override
public Object getMessage() {
return encryptedMessage;
}
}
}