View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.filter.ssl;
21  
22  import java.net.InetSocketAddress;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import javax.net.ssl.SSLContext;
27  import javax.net.ssl.SSLEngine;
28  import javax.net.ssl.SSLException;
29  import javax.net.ssl.SSLHandshakeException;
30  import javax.net.ssl.SSLSession;
31  
32  import org.apache.mina.core.buffer.IoBuffer;
33  import org.apache.mina.core.filterchain.IoFilter;
34  import org.apache.mina.core.filterchain.IoFilterAdapter;
35  import org.apache.mina.core.filterchain.IoFilterChain;
36  import org.apache.mina.core.future.DefaultWriteFuture;
37  import org.apache.mina.core.future.IoFuture;
38  import org.apache.mina.core.future.IoFutureListener;
39  import org.apache.mina.core.future.WriteFuture;
40  import org.apache.mina.core.service.IoAcceptor;
41  import org.apache.mina.core.service.IoHandler;
42  import org.apache.mina.core.session.AttributeKey;
43  import org.apache.mina.core.session.IoSession;
44  import org.apache.mina.core.write.WriteRequest;
45  import org.apache.mina.core.write.WriteRequestWrapper;
46  import org.apache.mina.core.write.WriteToClosedSessionException;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  /**
51   * An SSL filter that encrypts and decrypts the data exchanged in the session.
52   * Adding this filter triggers SSL handshake procedure immediately by sending
53   * a SSL 'hello' message, so you don't need to call
54   * {@link #startSsl(IoSession)} manually unless you are implementing StartTLS
55   * (see below).  If you don't want the handshake procedure to start
56   * immediately, please specify {@code false} as {@code autoStart} parameter in
57   * the constructor.
58   * <p>
59   * This filter uses an {@link SSLEngine} which was introduced in Java 5, so
60   * Java version 5 or above is mandatory to use this filter. And please note that
61   * this filter only works for TCP/IP connections.
62   *
63   * <h2>Implementing StartTLS</h2>
64   * <p>
65   * You can use {@link #DISABLE_ENCRYPTION_ONCE} attribute to implement StartTLS:
66   * <pre>
67   * public void messageReceived(IoSession session, Object message) {
68   *    if (message instanceof MyStartTLSRequest) {
69   *        // Insert SSLFilter to get ready for handshaking
70   *        session.getFilterChain().addFirst(sslFilter);
71   *
72   *        // Disable encryption temporarily.
73   *        // This attribute will be removed by SSLFilter
74   *        // inside the Session.write() call below.
75   *        session.setAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE);
76   *
77   *        // Write StartTLSResponse which won't be encrypted.
78   *        session.write(new MyStartTLSResponse(OK));
79   *
80   *        // Now DISABLE_ENCRYPTION_ONCE attribute is cleared.
81   *        assert session.getAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE) == null;
82   *    }
83   * }
84   * </pre>
85   *
86   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
87   * @org.apache.xbean.XBean
88   */
89  public class SslFilter extends IoFilterAdapter {
90      /** The logger */
91      private static final Logger LOGGER = LoggerFactory.getLogger(SslFilter.class);
92  
93      /**
94       * A session attribute key that stores underlying {@link SSLSession}
95       * for each session.
96       */
97      public static final AttributeKey SSL_SESSION = new AttributeKey(SslFilter.class, "session");
98  
99      /**
100      * A session attribute key that makes next one write request bypass
101      * this filter (not encrypting the data).  This is a marker attribute,
102      * which means that you can put whatever as its value. ({@link Boolean#TRUE}
103      * is preferred.)  The attribute is automatically removed from the session
104      * attribute map as soon as {@link IoSession#write(Object)} is invoked,
105      * and therefore should be put again if you want to make more messages
106      * bypass this filter.  This is especially useful when you implement
107      * StartTLS.
108      */
109     public static final AttributeKey DISABLE_ENCRYPTION_ONCE = new AttributeKey(SslFilter.class, "disableOnce");
110 
111     /**
112      * A session attribute key that makes this filter to emit a
113      * {@link IoHandler#messageReceived(IoSession, Object)} event with a
114      * special message ({@link #SESSION_SECURED} or {@link #SESSION_UNSECURED}).
115      * This is a marker attribute, which means that you can put whatever as its
116      * value. ({@link Boolean#TRUE} is preferred.)  By default, this filter
117      * doesn't emit any events related with SSL session flow control.
118      */
119     public static final AttributeKey USE_NOTIFICATION = new AttributeKey(SslFilter.class, "useNotification");
120 
121     /**
122      * A session attribute key that should be set to an {@link InetSocketAddress}.
123      * Setting this attribute causes
124      * {@link SSLContext#createSSLEngine(String, int)} to be called passing the
125      * hostname and port of the {@link InetSocketAddress} to get an
126      * {@link SSLEngine} instance. If not set {@link SSLContext#createSSLEngine()}
127      * will be called.
128      * <br>
129      * Using this feature {@link SSLSession} objects may be cached and reused
130      * when in client mode.
131      *
132      * @see SSLContext#createSSLEngine(String, int)
133      */
134     public static final AttributeKey PEER_ADDRESS = new AttributeKey(SslFilter.class, "peerAddress");
135 
136     /**
137      * A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)}
138      * event when the session is secured and its {@link #USE_NOTIFICATION}
139      * attribute is set.
140      */
141     public static final SslFilterMessage SESSION_SECURED = new SslFilterMessage("SESSION_SECURED");
142 
143     /**
144      * A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)}
145      * event when the session is not secure anymore and its {@link #USE_NOTIFICATION}
146      * attribute is set.
147      */
148     public static final SslFilterMessage SESSION_UNSECURED = new SslFilterMessage("SESSION_UNSECURED");
149 
150     /** An attribute containing the next filter */
151     private static final AttributeKey NEXT_FILTER = new AttributeKey(SslFilter.class, "nextFilter");
152 
153     private static final AttributeKey SSL_HANDLER = new AttributeKey(SslFilter.class, "handler");
154 
155     /** The SslContext used */
156     /* No qualifier */final SSLContext sslContext;
157 
158     /** A flag used to tell the filter to start the handshake immediately */
159     private final boolean autoStart;
160 
161     /** A flag used to determinate if the handshake should start immediately */
162     public static final boolean START_HANDSHAKE = true;
163 
164     /** A flag used to determinate if the handshake should wait for the client to initiate the handshake */
165     public static final boolean CLIENT_HANDSHAKE = false;
166 
167     private boolean client;
168 
169     private boolean needClientAuth;
170 
171     private boolean wantClientAuth;
172 
173     private String[] enabledCipherSuites;
174 
175     private String[] enabledProtocols;
176 
177     /**
178      * Creates a new SSL filter using the specified {@link SSLContext}.
179      * The handshake will start immediately after the filter has been added
180      * to the chain.
181      * 
182      * @param sslContext The SSLContext to use
183      */
184     public SslFilter(SSLContext sslContext) {
185         this(sslContext, START_HANDSHAKE);
186     }
187 
188     /**
189      * Creates a new SSL filter using the specified {@link SSLContext}.
190      * If the <tt>autostart</tt> flag is set to <tt>true</tt>, the
191      * handshake will start immediately after the filter has been added
192      * to the chain.
193      * 
194      * @param sslContext The SSLContext to use
195      * @param autoStart The flag used to tell the filter to start the handshake immediately
196      */
197     public SslFilter(SSLContext sslContext, boolean autoStart) {
198         if (sslContext == null) {
199             throw new IllegalArgumentException("sslContext");
200         }
201 
202         this.sslContext = sslContext;
203         this.autoStart = autoStart;
204     }
205 
206     /**
207      * Returns the underlying {@link SSLSession} for the specified session.
208      *
209      * @param session The current session 
210      * @return <tt>null</tt> if no {@link SSLSession} is initialized yet.
211      */
212     public SSLSession getSslSession(IoSession session) {
213         return (SSLSession) session.getAttribute(SSL_SESSION);
214     }
215 
216     /**
217      * (Re)starts SSL session for the specified <tt>session</tt> if not started yet.
218      * Please note that SSL session is automatically started by default, and therefore
219      * you don't need to call this method unless you've used TLS closure.
220      *
221      * @param session The session that will be switched to SSL mode
222      * @return <tt>true</tt> if the SSL session has been started, <tt>false</tt> if already started.
223      * @throws SSLException if failed to start the SSL session
224      */
225     public boolean startSsl(IoSession session) throws SSLException {
226         SslHandler sslHandler = getSslSessionHandler(session);
227         boolean started;
228 
229         try {
230             synchronized (sslHandler) {
231                 if (sslHandler.isOutboundDone()) {
232                     NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER);
233                     sslHandler.destroy();
234                     sslHandler.init();
235                     sslHandler.handshake(nextFilter);
236                     started = true;
237                 } else {
238                     started = false;
239                 }
240             }
241 
242             sslHandler.flushScheduledEvents();
243         } catch (SSLException se) {
244             sslHandler.release();
245             throw se;
246         }
247 
248         return started;
249     }
250 
251     /**
252      * An extended toString() method for sessions. If the SSL handshake
253      * is not yet completed, we will print (ssl) in small caps. Once it's
254      * completed, we will use SSL capitalized.
255      */
256     /* no qualifier */String getSessionInfo(IoSession session) {
257         StringBuilder sb = new StringBuilder();
258 
259         if (session.getService() instanceof IoAcceptor) {
260             sb.append("Session Server");
261 
262         } else {
263             sb.append("Session Client");
264         }
265 
266         sb.append('[').append(session.getId()).append(']');
267 
268         SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
269 
270         if (sslHandler == null) {
271             sb.append("(no sslEngine)");
272         } else if (isSslStarted(session)) {
273             if (sslHandler.isHandshakeComplete()) {
274                 sb.append("(SSL)");
275             } else {
276                 sb.append("(ssl...)");
277             }
278         }
279 
280         return sb.toString();
281     }
282 
283     /**
284      * @return <tt>true</tt> if and only if the specified <tt>session</tt> is
285      * encrypted/decrypted over SSL/TLS currently. This method will start
286      * to return <tt>false</tt> after TLS <tt>close_notify</tt> message
287      * is sent and any messages written after then is not going to get encrypted.
288      * 
289      * @param session the session we want to check
290      */
291     public boolean isSslStarted(IoSession session) {
292         SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
293 
294         if (sslHandler == null) {
295             return false;
296         }
297 
298         synchronized (sslHandler) {
299             return !sslHandler.isOutboundDone();
300         }
301     }
302 
303     /**
304      * @return <tt>true</tt> if and only if the conditions for
305      * {@link #isSslStarted(IoSession)} are met, and the handhake has
306      * completed.
307      *
308      * @param session the session we want to check
309      */
310     public boolean isSecured(IoSession session) {
311         SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
312 
313         if (sslHandler == null) {
314             return false;
315         }
316 
317         synchronized (sslHandler) {
318             return !sslHandler.isOutboundDone() && sslHandler.isHandshakeComplete();
319         }
320     }
321 
322 
323     /**
324      * Stops the SSL session by sending TLS <tt>close_notify</tt> message to
325      * initiate TLS closure.
326      *
327      * @param session the {@link IoSession} to initiate TLS closure
328      * @return The Future for the initiated closure
329      * @throws SSLException if failed to initiate TLS closure
330      */
331     public WriteFuture stopSsl(IoSession session) throws SSLException {
332         SslHandler sslHandler = getSslSessionHandler(session);
333         NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER);
334         WriteFuture future;
335 
336         try {
337             synchronized (sslHandler) {
338                 future = initiateClosure(nextFilter, session);
339             }
340 
341             sslHandler.flushScheduledEvents();
342         } catch (SSLException se) {
343             sslHandler.release();
344             throw se;
345         }
346 
347         return future;
348     }
349 
350     /**
351      * @return <tt>true</tt> if the engine is set to use client mode
352      * when handshaking.
353      */
354     public boolean isUseClientMode() {
355         return client;
356     }
357 
358     /**
359      * Configures the engine to use client (or server) mode when handshaking.
360      * 
361      * @param clientMode <tt>true</tt> when we are in client mode, <tt>false</tt> when in server mode
362      */
363     public void setUseClientMode(boolean clientMode) {
364         this.client = clientMode;
365     }
366 
367     /**
368      * @return <tt>true</tt> if the engine will <em>require</em> client authentication.
369      * This option is only useful to engines in the server mode.
370      */
371     public boolean isNeedClientAuth() {
372         return needClientAuth;
373     }
374 
375     /**
376      * Configures the engine to <em>require</em> client authentication.
377      * This option is only useful for engines in the server mode.
378      * 
379      * @param needClientAuth A flag set when we need to authenticate the client
380      */
381     public void setNeedClientAuth(boolean needClientAuth) {
382         this.needClientAuth = needClientAuth;
383     }
384 
385     /**
386      * @return <tt>true</tt> if the engine will <em>request</em> client authentication.
387      * This option is only useful to engines in the server mode.
388      */
389     public boolean isWantClientAuth() {
390         return wantClientAuth;
391     }
392 
393     /**
394      * Configures the engine to <em>request</em> client authentication.
395      * This option is only useful for engines in the server mode.
396      * 
397      * @param wantClientAuth A flag set when we want to check the client authentication
398      */
399     public void setWantClientAuth(boolean wantClientAuth) {
400         this.wantClientAuth = wantClientAuth;
401     }
402 
403     /**
404      * @return the list of cipher suites to be enabled when {@link SSLEngine}
405      * is initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.'
406      */
407     public String[] getEnabledCipherSuites() {
408         return enabledCipherSuites;
409     }
410 
411     /**
412      * Sets the list of cipher suites to be enabled when {@link SSLEngine}
413      * is initialized.
414      *
415      * @param cipherSuites <tt>null</tt> means 'use {@link SSLEngine}'s default.'
416      */
417     public void setEnabledCipherSuites(String[] cipherSuites) {
418         this.enabledCipherSuites = cipherSuites;
419     }
420 
421     /**
422      * @return the list of protocols to be enabled when {@link SSLEngine}
423      * is initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.'
424      */
425     public String[] getEnabledProtocols() {
426         return enabledProtocols;
427     }
428 
429     /**
430      * Sets the list of protocols to be enabled when {@link SSLEngine}
431      * is initialized.
432      *
433      * @param protocols <tt>null</tt> means 'use {@link SSLEngine}'s default.'
434      */
435     public void setEnabledProtocols(String[] protocols) {
436         this.enabledProtocols = protocols;
437     }
438 
439     /**
440      * Executed just before the filter is added into the chain, we do :
441      * <ul>
442      * <li>check that we don't have a SSL filter already present
443      * <li>we update the next filter
444      * <li>we create the SSL handler helper class
445      * <li>and we store it into the session's Attributes
446      * </ul>
447      */
448     @Override
449     public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
450         // Check that we don't have a SSL filter already present in the chain
451         if (parent.contains(SslFilter.class)) {
452             String msg = "Only one SSL filter is permitted in a chain.";
453             LOGGER.error(msg);
454             throw new IllegalStateException(msg);
455         }
456 
457         LOGGER.debug("Adding the SSL Filter {} to the chain", name);
458 
459         IoSession session = parent.getSession();
460         session.setAttribute(NEXT_FILTER, nextFilter);
461 
462         // Create a SSL handler and start handshake.
463         SslHandler sslHandler = new SslHandler(this, session);
464         
465         // Adding the supported ciphers in the SSLHandler
466         if ((enabledCipherSuites == null) || (enabledCipherSuites.length == 0)) {
467             enabledCipherSuites = sslContext.getServerSocketFactory().getSupportedCipherSuites();
468         }
469 
470         sslHandler.init();
471 
472         session.setAttribute(SSL_HANDLER, sslHandler);
473     }
474 
475     @Override
476     public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
477         if (autoStart == START_HANDSHAKE) {
478             initiateHandshake(nextFilter, parent.getSession());
479         }
480     }
481 
482     @Override
483     public void onPreRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
484         IoSession session = parent.getSession();
485         stopSsl(session);
486         session.removeAttribute(NEXT_FILTER);
487         session.removeAttribute(SSL_HANDLER);
488     }
489 
490     // IoFilter impl.
491     @Override
492     public void sessionClosed(NextFilter nextFilter, IoSession session) throws SSLException {
493         SslHandler sslHandler = getSslSessionHandler(session);
494 
495         try {
496             synchronized (sslHandler) {
497                 // release resources
498                 sslHandler.destroy();
499             }
500         } finally {
501             // notify closed session
502             nextFilter.sessionClosed(session);
503         }
504     }
505 
506     @Override
507     public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws SSLException {
508         if (LOGGER.isDebugEnabled()) {
509             LOGGER.debug("{}: Message received : {}", getSessionInfo(session), message);
510         }
511 
512         SslHandler sslHandler = getSslSessionHandler(session);
513 
514         synchronized (sslHandler) {
515             if (!isSslStarted(session) && sslHandler.isInboundDone()) {
516                 // The SSL session must be established first before we
517                 // can push data to the application. Store the incoming
518                 // data into a queue for a later processing
519                 sslHandler.scheduleMessageReceived(nextFilter, message);
520             } else {
521                 IoBuffer buf = (IoBuffer) message;
522 
523                 try {
524                     if (sslHandler.isOutboundDone()) {
525                         sslHandler.destroy();
526                         throw new SSLException("Outbound done");
527                     }
528                     
529                     // forward read encrypted data to SSL handler
530                     sslHandler.messageReceived(nextFilter, buf.buf());
531 
532                     // Handle data to be forwarded to application or written to net
533                     handleSslData(nextFilter, sslHandler);
534 
535                     if (sslHandler.isInboundDone()) {
536                         if (sslHandler.isOutboundDone()) {
537                             sslHandler.destroy();
538                         } else {
539                             initiateClosure(nextFilter, session);
540                         }
541 
542                         if (buf.hasRemaining()) {
543                             // Forward the data received after closure.
544                             sslHandler.scheduleMessageReceived(nextFilter, buf);
545                         }
546                     }
547                 } catch (SSLException ssle) {
548                     if (!sslHandler.isHandshakeComplete()) {
549                         SSLException newSsle = new SSLHandshakeException("SSL handshake failed.");
550                         newSsle.initCause(ssle);
551                         ssle = newSsle;
552                         
553                         // Close the session immediately, the handshake has failed
554                         session.closeNow();
555                     } else {
556                         // Free the SSL Handler buffers
557                         sslHandler.release();
558                     }
559 
560                     throw ssle;
561                 }
562             }
563         }
564 
565         sslHandler.flushScheduledEvents();
566     }
567 
568     @Override
569     public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) {
570         if (writeRequest instanceof EncryptedWriteRequest) {
571             EncryptedWriteRequest wrappedRequest = (EncryptedWriteRequest) writeRequest;
572             nextFilter.messageSent(session, wrappedRequest.getParentRequest());
573         } else {
574             // ignore extra buffers used for handshaking
575         }
576     }
577 
578     @Override
579     public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception {
580 
581         if (cause instanceof WriteToClosedSessionException) {
582             // Filter out SSL close notify, which is likely to fail to flush
583             // due to disconnection.
584             WriteToClosedSessionException e = (WriteToClosedSessionException) cause;
585             List<WriteRequest> failedRequests = e.getRequests();
586             boolean containsCloseNotify = false;
587 
588             for (WriteRequest r : failedRequests) {
589                 if (isCloseNotify(r.getMessage())) {
590                     containsCloseNotify = true;
591                     break;
592                 }
593             }
594 
595             if (containsCloseNotify) {
596                 if (failedRequests.size() == 1) {
597                     // close notify is the only failed request; bail out.
598                     return;
599                 }
600 
601                 List<WriteRequest> newFailedRequests = new ArrayList<>(failedRequests.size() - 1);
602 
603                 for (WriteRequest r : failedRequests) {
604                     if (!isCloseNotify(r.getMessage())) {
605                         newFailedRequests.add(r);
606                     }
607                 }
608 
609                 if (newFailedRequests.isEmpty()) {
610                     // the failedRequests were full with close notify; bail out.
611                     return;
612                 }
613 
614                 cause = new WriteToClosedSessionException(newFailedRequests, cause.getMessage(), cause.getCause());
615             }
616         }
617 
618         nextFilter.exceptionCaught(session, cause);
619     }
620 
621     private boolean isCloseNotify(Object message) {
622         if (!(message instanceof IoBuffer)) {
623             return false;
624         }
625 
626         IoBuffer buf = (IoBuffer) message;
627         int offset = buf.position();
628 
629         return (buf.get(offset + 0) == 0x15) /* Alert */
630                 && (buf.get(offset + 1) == 0x03) /* TLS/SSL */
631                 && ((buf.get(offset + 2) == 0x00) /* SSL 3.0 */
632                         || (buf.get(offset + 2) == 0x01) /* TLS 1.0 */
633                         || (buf.get(offset + 2) == 0x02) /* TLS 1.1 */
634                         || (buf.get(offset + 2) == 0x03)) /* TLS 1.2 */
635                         && (buf.get(offset + 3) == 0x00); /* close_notify */
636     }
637 
638     @Override
639     public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws SSLException {
640         if (LOGGER.isDebugEnabled()) {
641             LOGGER.debug("{}: Writing Message : {}", getSessionInfo(session), writeRequest);
642         }
643 
644         boolean needsFlush = true;
645         SslHandler sslHandler = getSslSessionHandler(session);
646 
647         try {
648             synchronized (sslHandler) {
649                 if (!isSslStarted(session)) {
650                     sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
651                 }
652                 // Don't encrypt the data if encryption is disabled.
653                 else if (session.containsAttribute(DISABLE_ENCRYPTION_ONCE)) {
654                     // Remove the marker attribute because it is temporary.
655                     session.removeAttribute(DISABLE_ENCRYPTION_ONCE);
656                     sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
657                 } else {
658                     // Otherwise, encrypt the buffer.
659                     IoBuffer buf = (IoBuffer) writeRequest.getMessage();
660 
661                     if (sslHandler.isWritingEncryptedData()) {
662                         // data already encrypted; simply return buffer
663                         sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
664                     } else if (sslHandler.isHandshakeComplete()) {
665                         // SSL encrypt
666                         buf.mark();
667                         sslHandler.encrypt(buf.buf());
668                         IoBuffer encryptedBuffer = sslHandler.fetchOutNetBuffer();
669                         sslHandler.scheduleFilterWrite(nextFilter, new EncryptedWriteRequest(writeRequest,
670                                 encryptedBuffer));
671                     } else {
672                         if (session.isConnected()) {
673                             // Handshake not complete yet.
674                             sslHandler.schedulePreHandshakeWriteRequest(nextFilter, writeRequest);
675                         }
676 
677                         needsFlush = false;
678                     }
679                 }
680             }
681 
682             if (needsFlush) {
683                 sslHandler.flushScheduledEvents();
684             }
685         } catch (SSLException se) {
686             sslHandler.release();
687             throw se;
688         }
689     }
690 
691     @Override
692     public void filterClose(final NextFilter nextFilter, final IoSession session) throws SSLException {
693         SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
694 
695         if (sslHandler == null) {
696             // The connection might already have closed, or
697             // SSL might have not started yet.
698             nextFilter.filterClose(session);
699             return;
700         }
701 
702         WriteFuture future = null;
703 
704         try {
705             synchronized (sslHandler) {
706                 if (isSslStarted(session)) {
707                     future = initiateClosure(nextFilter, session);
708                     future.addListener(new IoFutureListener<IoFuture>() {
709                         @Override
710                         public void operationComplete(IoFuture future) {
711                             nextFilter.filterClose(session);
712                         }
713                     });
714                 }
715             }
716 
717             sslHandler.flushScheduledEvents();
718         } catch (SSLException se) {
719             sslHandler.release();
720             throw se;
721         } finally {
722             if (future == null) {
723                 nextFilter.filterClose(session);
724             }
725         }
726     }
727 
728     /**
729      * Initiate the SSL handshake. This can be invoked if you have set the 'autoStart' to
730      * false when creating the SslFilter instance.
731      * 
732      * @param session The session for which the SSL handshake should be done
733      * @throws SSLException If the handshake failed
734      */
735     public void initiateHandshake(IoSession session) throws SSLException {
736         IoFilterChain filterChain = session.getFilterChain();
737         
738         if (filterChain == null) {
739             throw new SSLException("No filter chain");
740         }
741         
742         IoFilter.NextFilter nextFilter = filterChain.getNextFilter(SslFilter.class);
743         
744         if (nextFilter == null) {
745             throw new SSLException("No SSL next filter in the chain");
746         }
747         
748         initiateHandshake(nextFilter, session);
749     }
750 
751     private void initiateHandshake(NextFilter nextFilter, IoSession session) throws SSLException {
752         LOGGER.debug("{} : Starting the first handshake", getSessionInfo(session));
753         SslHandler sslHandler = getSslSessionHandler(session);
754 
755         try {
756             synchronized (sslHandler) {
757                 sslHandler.handshake(nextFilter);
758             }
759 
760             sslHandler.flushScheduledEvents();
761         } catch (SSLException se) {
762             sslHandler.release();
763             throw se;
764         }
765     }
766 
767     private WriteFuture initiateClosure(NextFilter nextFilter, IoSession session) throws SSLException {
768         SslHandler sslHandler = getSslSessionHandler(session);
769         WriteFuture future = null;
770 
771         // if already shut down
772         try {
773             synchronized(sslHandler) {
774                 if (!sslHandler.closeOutbound()) {
775                     return DefaultWriteFuture.newNotWrittenFuture(session, new IllegalStateException(
776                             "SSL session is shut down already."));
777                 }
778     
779                 // there might be data to write out here?
780                 future = sslHandler.writeNetBuffer(nextFilter);
781     
782                 if (future == null) {
783                     future = DefaultWriteFuture.newWrittenFuture(session);
784                 }
785     
786                 if (sslHandler.isInboundDone()) {
787                     sslHandler.destroy();
788                 }
789             }
790 
791             if (session.containsAttribute(USE_NOTIFICATION)) {
792                 sslHandler.scheduleMessageReceived(nextFilter, SESSION_UNSECURED);
793             }
794             
795         } catch (SSLException se) {
796             sslHandler.release();
797             throw se;
798         }
799 
800         return future;
801     }
802 
803     // Utilities
804     private void handleSslData(NextFilter nextFilter, SslHandler sslHandler) throws SSLException {
805         if (LOGGER.isDebugEnabled()) {
806             LOGGER.debug("{}: Processing the SSL Data ", getSessionInfo(sslHandler.getSession()));
807         }
808 
809         // Flush any buffered write requests occurred before handshaking.
810         if (sslHandler.isHandshakeComplete()) {
811             sslHandler.flushPreHandshakeEvents();
812         }
813 
814         // Write encrypted data to be written (if any)
815         sslHandler.writeNetBuffer(nextFilter);
816 
817         // handle app. data read (if any)
818         handleAppDataRead(nextFilter, sslHandler);
819     }
820 
821     private void handleAppDataRead(NextFilter nextFilter, SslHandler sslHandler) {
822         // forward read app data
823         IoBuffer readBuffer = sslHandler.fetchAppBuffer();
824 
825         if (readBuffer.hasRemaining()) {
826             sslHandler.scheduleMessageReceived(nextFilter, readBuffer);
827         }
828     }
829 
830     private SslHandler getSslSessionHandler(IoSession session) {
831         SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
832 
833         if (sslHandler == null) {
834             throw new IllegalStateException();
835         }
836 
837         synchronized(sslHandler) {
838             if (sslHandler.getSslFilter() != this) {
839                 throw new IllegalArgumentException("Not managed by this filter.");
840             }
841         }
842 
843         return sslHandler;
844     }
845 
846     /**
847      * A message that is sent from {@link SslFilter} when the connection became
848      * secure or is not secure anymore.
849      *
850      * @author <a href="http://mina.apache.org">Apache MINA Project</a>
851      */
852     public static class SslFilterMessage {
853         private final String name;
854 
855         private SslFilterMessage(String name) {
856             this.name = name;
857         }
858 
859         @Override
860         public String toString() {
861             return name;
862         }
863     }
864 
865     private static class EncryptedWriteRequest extends WriteRequestWrapper {
866         private final IoBuffer encryptedMessage;
867 
868         private EncryptedWriteRequest(WriteRequest writeRequest, IoBuffer encryptedMessage) {
869             super(writeRequest);
870             this.encryptedMessage = encryptedMessage;
871         }
872 
873         @Override
874         public Object getMessage() {
875             return encryptedMessage;
876         }
877     }
878 }