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.DefaultWriteRequest;
45  import org.apache.mina.core.write.WriteRequest;
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 AttributeKeyteKey.html#AttributeKey">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 AttributeKeyttributeKey">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 SslEvent#SECURED} or {@link SslEvent#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.html#AttributeKey">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 AttributeKeyeKey.html#AttributeKey">AttributeKey PEER_ADDRESS = new AttributeKey(SslFilter.class, "peerAddress");
135 
136     /** An attribute containing the next filter */
137     private static final AttributeKeyteKey.html#AttributeKey">AttributeKey NEXT_FILTER = new AttributeKey(SslFilter.class, "nextFilter");
138 
139     private static final AttributeKeyteKey.html#AttributeKey">AttributeKey SSL_HANDLER = new AttributeKey(SslFilter.class, "handler");
140 
141     /** The SslContext used */
142     /* No qualifier */final SSLContext sslContext;
143 
144     /** A flag used to tell the filter to start the handshake immediately */
145     private final boolean autoStart;
146 
147     /** A flag used to determinate if the handshake should start immediately */
148     public static final boolean START_HANDSHAKE = true;
149 
150     /** A flag used to determinate if the handshake should wait for the client to initiate the handshake */
151     public static final boolean CLIENT_HANDSHAKE = false;
152 
153     private boolean client;
154 
155     private boolean needClientAuth;
156 
157     private boolean wantClientAuth;
158 
159     private String[] enabledCipherSuites;
160 
161     private String[] enabledProtocols;
162 
163     /**
164      * Creates a new SSL filter using the specified {@link SSLContext}.
165      * The handshake will start immediately after the filter has been added
166      * to the chain.
167      * 
168      * @param sslContext The SSLContext to use
169      */
170     public SslFilter(SSLContext sslContext) {
171         this(sslContext, START_HANDSHAKE);
172     }
173 
174     /**
175      * Creates a new SSL filter using the specified {@link SSLContext}.
176      * If the <tt>autostart</tt> flag is set to <tt>true</tt>, the
177      * handshake will start immediately after the filter has been added
178      * to the chain.
179      * 
180      * @param sslContext The SSLContext to use
181      * @param autoStart The flag used to tell the filter to start the handshake immediately
182      */
183     public SslFilter(SSLContext sslContext, boolean autoStart) {
184         if (sslContext == null) {
185             throw new IllegalArgumentException("sslContext");
186         }
187 
188         this.sslContext = sslContext;
189         this.autoStart = autoStart;
190     }
191 
192     /**
193      * Returns the underlying {@link SSLSession} for the specified session.
194      *
195      * @param session The current session 
196      * @return <tt>null</tt> if no {@link SSLSession} is initialized yet.
197      */
198     public SSLSession getSslSession(IoSession session) {
199         return (SSLSession) session.getAttribute(SSL_SESSION);
200     }
201 
202     /**
203      * (Re)starts SSL session for the specified <tt>session</tt> if not started yet.
204      * Please note that SSL session is automatically started by default, and therefore
205      * you don't need to call this method unless you've used TLS closure.
206      *
207      * @param session The session that will be switched to SSL mode
208      * @return <tt>true</tt> if the SSL session has been started, <tt>false</tt> if already started.
209      * @throws SSLException if failed to start the SSL session
210      */
211     public boolean startSsl(IoSession session) throws SSLException {
212         SslHandler sslHandler = getSslSessionHandler(session);
213         boolean started;
214 
215         try {
216             synchronized (sslHandler) {
217                 if (sslHandler.isOutboundDone()) {
218                     NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER);
219                     sslHandler.destroy();
220                     sslHandler.init();
221                     sslHandler.handshake(nextFilter);
222                     started = true;
223                 } else {
224                     started = false;
225                 }
226                 sslHandler.flushFilterWrite();
227             }
228             sslHandler.flushMessageReceived();
229         } catch (SSLException se) {
230             sslHandler.release();
231             throw se;
232         }
233 
234         return started;
235     }
236 
237     /**
238      * An extended toString() method for sessions. If the SSL handshake
239      * is not yet completed, we will print (ssl) in small caps. Once it's
240      * completed, we will use SSL capitalized.
241      */
242     /* no qualifier */String getSessionInfo(IoSession session) {
243         StringBuilder sb = new StringBuilder();
244 
245         if (session.getService() instanceof IoAcceptor) {
246             sb.append("Session Server");
247 
248         } else {
249             sb.append("Session Client");
250         }
251 
252         sb.append('[').append(session.getId()).append(']');
253 
254         SslHandler/../../org/apache/mina/filter/ssl/SslHandler.html#SslHandler">SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
255 
256         if (sslHandler == null) {
257             sb.append("(no sslEngine)");
258         } else if (isSslStarted(session)) {
259             if (sslHandler.isHandshakeComplete()) {
260                 sb.append("(SSL)");
261             } else {
262                 sb.append("(ssl...)");
263             }
264         }
265 
266         return sb.toString();
267     }
268 
269     /**
270      * @return <tt>true</tt> if and only if the specified <tt>session</tt> is
271      * encrypted/decrypted over SSL/TLS currently. This method will start
272      * to return <tt>false</tt> after TLS <tt>close_notify</tt> message
273      * is sent and any messages written after then is not going to get encrypted.
274      * 
275      * @param session the session we want to check
276      */
277     public boolean isSslStarted(IoSession session) {
278         SslHandler/../../org/apache/mina/filter/ssl/SslHandler.html#SslHandler">SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
279 
280         if (sslHandler == null) {
281             return false;
282         }
283 
284         synchronized (sslHandler) {
285             return !sslHandler.isOutboundDone();
286         }
287     }
288 
289     /**
290      * @return <tt>true</tt> if and only if the conditions for
291      * {@link #isSslStarted(IoSession)} are met, and the handhake has
292      * completed.
293      *
294      * @param session the session we want to check
295      */
296     public boolean isSecured(IoSession session) {
297         SslHandler/../../org/apache/mina/filter/ssl/SslHandler.html#SslHandler">SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
298 
299         if (sslHandler == null) {
300             return false;
301         }
302 
303         synchronized (sslHandler) {
304             return !sslHandler.isOutboundDone() && sslHandler.isHandshakeComplete();
305         }
306     }
307 
308 
309     /**
310      * Stops the SSL session by sending TLS <tt>close_notify</tt> message to
311      * initiate TLS closure.
312      *
313      * @param session the {@link IoSession} to initiate TLS closure
314      * @return The Future for the initiated closure
315      * @throws SSLException if failed to initiate TLS closure
316      */
317     public WriteFuture stopSsl(IoSession session) throws SSLException {
318         SslHandler sslHandler = getSslSessionHandler(session);
319         NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER);
320         WriteFuture future;
321 
322         try {
323             synchronized (sslHandler) {
324                 future = initiateClosure(nextFilter, session);
325                 sslHandler.flushFilterWrite();
326             }
327         } catch (SSLException se) {
328             sslHandler.release();
329             throw se;
330         }
331 
332         return future;
333     }
334 
335     /**
336      * @return <tt>true</tt> if the engine is set to use client mode
337      * when handshaking.
338      */
339     public boolean isUseClientMode() {
340         return client;
341     }
342 
343     /**
344      * Configures the engine to use client (or server) mode when handshaking.
345      * 
346      * @param clientMode <tt>true</tt> when we are in client mode, <tt>false</tt> when in server mode
347      */
348     public void setUseClientMode(boolean clientMode) {
349         this.client = clientMode;
350     }
351 
352     /**
353      * @return <tt>true</tt> if the engine will <em>require</em> client authentication.
354      * This option is only useful to engines in the server mode.
355      */
356     public boolean isNeedClientAuth() {
357         return needClientAuth;
358     }
359 
360     /**
361      * Configures the engine to <em>require</em> client authentication.
362      * This option is only useful for engines in the server mode.
363      * 
364      * @param needClientAuth A flag set when we need to authenticate the client
365      */
366     public void setNeedClientAuth(boolean needClientAuth) {
367         this.needClientAuth = needClientAuth;
368     }
369 
370     /**
371      * @return <tt>true</tt> if the engine will <em>request</em> client authentication.
372      * This option is only useful to engines in the server mode.
373      */
374     public boolean isWantClientAuth() {
375         return wantClientAuth;
376     }
377 
378     /**
379      * Configures the engine to <em>request</em> client authentication.
380      * This option is only useful for engines in the server mode.
381      * 
382      * @param wantClientAuth A flag set when we want to check the client authentication
383      */
384     public void setWantClientAuth(boolean wantClientAuth) {
385         this.wantClientAuth = wantClientAuth;
386     }
387 
388     /**
389      * @return the list of cipher suites to be enabled when {@link SSLEngine}
390      * is initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.'
391      */
392     public String[] getEnabledCipherSuites() {
393         return enabledCipherSuites;
394     }
395 
396     /**
397      * Sets the list of cipher suites to be enabled when {@link SSLEngine}
398      * is initialized.
399      *
400      * @param cipherSuites <tt>null</tt> means 'use {@link SSLEngine}'s default.'
401      */
402     public void setEnabledCipherSuites(String[] cipherSuites) {
403         this.enabledCipherSuites = cipherSuites;
404     }
405 
406     /**
407      * @return the list of protocols to be enabled when {@link SSLEngine}
408      * is initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.'
409      */
410     public String[] getEnabledProtocols() {
411         return enabledProtocols;
412     }
413 
414     /**
415      * Sets the list of protocols to be enabled when {@link SSLEngine}
416      * is initialized.
417      *
418      * @param protocols <tt>null</tt> means 'use {@link SSLEngine}'s default.'
419      */
420     public void setEnabledProtocols(String[] protocols) {
421         this.enabledProtocols = protocols;
422     }
423 
424     /**
425      * Executed just before the filter is added into the chain, we do :
426      * <ul>
427      * <li>check that we don't have a SSL filter already present
428      * <li>we update the next filter
429      * <li>we create the SSL handler helper class
430      * <li>and we store it into the session's Attributes
431      * </ul>
432      */
433     @Override
434     public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
435         // Check that we don't have a SSL filter already present in the chain
436         if (parent.contains(SslFilter.class)) {
437             String msg = "Only one SSL filter is permitted in a chain.";
438             LOGGER.error(msg);
439             throw new IllegalStateException(msg);
440         }
441 
442         if (LOGGER.isDebugEnabled()) {
443             LOGGER.debug("Adding the SSL Filter {} to the chain", name);
444         }
445 
446         IoSession session = parent.getSession();
447         session.setAttribute(NEXT_FILTER, nextFilter);
448 
449         // Create a SSL handler and start handshake.
450         SslHandlerdler.html#SslHandler">SslHandler sslHandler = new SslHandler(this, session);
451         
452         // Adding the supported ciphers in the SSLHandler
453         if ((enabledCipherSuites == null) || (enabledCipherSuites.length == 0)) {
454             enabledCipherSuites = sslContext.getServerSocketFactory().getSupportedCipherSuites();
455         }
456 
457         sslHandler.init();
458 
459         session.setAttribute(SSL_HANDLER, sslHandler);
460     }
461 
462     @Override
463     public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
464         if (autoStart == START_HANDSHAKE) {
465             initiateHandshake(nextFilter, parent.getSession());
466         }
467     }
468 
469     @Override
470     public void onPreRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
471         IoSession session = parent.getSession();
472         stopSsl(session);
473         session.removeAttribute(NEXT_FILTER);
474         session.removeAttribute(SSL_HANDLER);
475     }
476 
477     // IoFilter impl.
478     @Override
479     public void sessionClosed(NextFilter nextFilter, IoSession session) throws SSLException {
480         SslHandler sslHandler = getSslSessionHandler(session);
481 
482         try {
483             synchronized (sslHandler) {
484                 // release resources
485                 sslHandler.destroy();
486             }
487         } finally {
488             // notify closed session
489             nextFilter.sessionClosed(session);
490         }
491     }
492 
493     @Override
494     public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws SSLException {
495         if (LOGGER.isDebugEnabled()) {
496             LOGGER.debug("{}: Message received : {}", getSessionInfo(session), message);
497         }
498 
499         SslHandler sslHandler = getSslSessionHandler(session);
500 
501 	synchronized (sslHandler) {
502 	    if (!isSslStarted(session) && sslHandler.isInboundDone()) {
503 		// The SSL session must be established first before we
504 		// can push data to the application. Store the incoming
505 		// data into a queue for a later processing
506 		sslHandler.scheduleMessageReceived(nextFilter, message);
507 	    } else {
508 		IoBuffer buf = (IoBuffer) message;
509 
510 		try {
511 		    if (sslHandler.isOutboundDone()) {
512 			sslHandler.destroy();
513 			throw new SSLException("Outbound done");
514 		    }
515 
516 		    // forward read encrypted data to SSL handler
517 		    sslHandler.messageReceived(nextFilter, buf.buf());
518 
519 		    // Handle data to be forwarded to application or written to net
520 		    handleSslData(nextFilter, sslHandler);
521 
522 		    if (sslHandler.isInboundDone()) {
523 			if (sslHandler.isOutboundDone()) {
524 			    sslHandler.destroy();
525 			} else {
526 			    initiateClosure(nextFilter, session);
527 			}
528 
529 			if (buf.hasRemaining()) {
530 			    // Forward the data received after closure.
531 			    sslHandler.scheduleMessageReceived(nextFilter, buf);
532 			}
533 		    }
534 		} catch (SSLException ssle) {
535 		    if (!sslHandler.isHandshakeComplete()) {
536 			SSLException newSsle = new SSLHandshakeException("SSL handshake failed.");
537 			newSsle.initCause(ssle);
538 			ssle = newSsle;
539 
540 			// Close the session immediately, the handshake has failed
541 			session.closeNow();
542 		    } else {
543 			// Free the SSL Handler buffers
544 			sslHandler.release();
545 		    }
546 
547 		    throw ssle;
548 		}
549 	    }
550 	}
551 	
552 	sslHandler.flushMessageReceived();
553     }
554 
555     @Override
556     public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) {
557         if (writeRequest instanceof EncryptedWriteRequest) {
558             EncryptedWriteRequest wrappedRequest = (EncryptedWriteRequest) writeRequest;
559             nextFilter.messageSent(session, wrappedRequest.getParentRequest());
560         } else {
561             // ignore extra buffers used for handshaking
562         }
563     }
564 
565     @Override
566     public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception {
567 
568         if (cause instanceof WriteToClosedSessionException) {
569             // Filter out SSL close notify, which is likely to fail to flush
570             // due to disconnection.
571             WriteToClosedSessionException/apache/mina/core/write/WriteToClosedSessionException.html#WriteToClosedSessionException">WriteToClosedSessionException e = (WriteToClosedSessionException) cause;
572             List<WriteRequest> failedRequests = e.getRequests();
573             boolean containsCloseNotify = false;
574 
575             for (WriteRequest r : failedRequests) {
576                 if (isCloseNotify(r.getMessage())) {
577                     containsCloseNotify = true;
578                     break;
579                 }
580             }
581 
582             if (containsCloseNotify) {
583                 if (failedRequests.size() == 1) {
584                     // close notify is the only failed request; bail out.
585                     return;
586                 }
587 
588                 List<WriteRequest> newFailedRequests = new ArrayList<>(failedRequests.size() - 1);
589 
590                 for (WriteRequest r : failedRequests) {
591                     if (!isCloseNotify(r.getMessage())) {
592                         newFailedRequests.add(r);
593                     }
594                 }
595 
596                 if (newFailedRequests.isEmpty()) {
597                     // the failedRequests were full with close notify; bail out.
598                     return;
599                 }
600 
601                 cause = new WriteToClosedSessionException(newFailedRequests, cause.getMessage(), cause.getCause());
602             }
603         }
604 
605         nextFilter.exceptionCaught(session, cause);
606     }
607 
608     private boolean isCloseNotify(Object message) {
609         if (!(message instanceof IoBuffer)) {
610             return false;
611         }
612 
613         IoBuffer"../../../../../org/apache/mina/core/buffer/IoBuffer.html#IoBuffer">IoBuffer buf = (IoBuffer) message;
614         int offset = buf.position();
615 
616         return (buf.get(offset + 0) == 0x15) /* Alert */
617                 && (buf.get(offset + 1) == 0x03) /* TLS/SSL */
618                 && ((buf.get(offset + 2) == 0x00) /* SSL 3.0 */
619                         || (buf.get(offset + 2) == 0x01) /* TLS 1.0 */
620                         || (buf.get(offset + 2) == 0x02) /* TLS 1.1 */
621                         || (buf.get(offset + 2) == 0x03)) /* TLS 1.2 */
622                         && (buf.get(offset + 3) == 0x00); /* close_notify */
623     }
624 
625     @Override
626     public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws SSLException {
627         if (LOGGER.isDebugEnabled()) {
628             LOGGER.debug("{}: Writing Message : {}", getSessionInfo(session), writeRequest);
629         }
630 
631         boolean needsFlush = true;
632         SslHandler sslHandler = getSslSessionHandler(session);
633 
634         try {
635             synchronized (sslHandler) {
636                 if (!isSslStarted(session)) {
637                     sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
638                 }
639                 // Don't encrypt the data if encryption is disabled.
640                 else if (session.containsAttribute(DISABLE_ENCRYPTION_ONCE)) {
641                     // Remove the marker attribute because it is temporary.
642                     session.removeAttribute(DISABLE_ENCRYPTION_ONCE);
643                     sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
644                 } else {
645                     // Otherwise, encrypt the buffer.
646                     IoBuffer"../../../../../org/apache/mina/core/buffer/IoBuffer.html#IoBuffer">IoBuffer buf = (IoBuffer) writeRequest.getMessage();
647 
648                     if (sslHandler.isWritingEncryptedData()) {
649                         // data already encrypted; simply return buffer
650                         sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
651                     } else if (sslHandler.isHandshakeComplete()) {
652                         // SSL encrypt
653                         sslHandler.encrypt(buf.buf());
654                         IoBuffer encryptedBuffer = sslHandler.fetchOutNetBuffer();
655                         writeRequest.setMessage( encryptedBuffer );
656                         sslHandler.scheduleFilterWrite(nextFilter, new EncryptedWriteRequest(writeRequest,
657                             encryptedBuffer));
658                     } else {
659                         if (session.isConnected()) {
660                             // Handshake not complete yet.
661                             sslHandler.schedulePreHandshakeWriteRequest(nextFilter, writeRequest);
662                         }
663 
664                         needsFlush = false;
665                     }
666                 }
667                 if (needsFlush) {
668                     sslHandler.flushFilterWrite();
669                 }
670             }
671         } catch (SSLException se) {
672             sslHandler.release();
673             throw se;
674         }
675     }
676 
677     @Override
678     public void filterClose(final NextFilter nextFilter, final IoSession session) throws SSLException {
679         SslHandler/../../org/apache/mina/filter/ssl/SslHandler.html#SslHandler">SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
680 
681         if (sslHandler == null) {
682             // The connection might already have closed, or
683             // SSL might have not started yet.
684             nextFilter.filterClose(session);
685             return;
686         }
687 
688         WriteFuture future = null;
689 
690         try {
691             synchronized (sslHandler) {
692                 if (isSslStarted(session)) {
693                     future = initiateClosure(nextFilter, session);
694                     future.addListener(new IoFutureListener<IoFuture>() {
695                         @Override
696                         public void operationComplete(IoFuture future) {
697                             nextFilter.filterClose(session);
698                         }
699                     });
700                 }
701                 sslHandler.flushFilterWrite();
702             }
703         } catch (SSLException se) {
704             sslHandler.release();
705             throw se;
706         } finally {
707             if (future == null) {
708                 nextFilter.filterClose(session);
709             }
710         }
711     }
712 
713     /**
714      * Initiate the SSL handshake. This can be invoked if you have set the 'autoStart' to
715      * false when creating the SslFilter instance.
716      * 
717      * @param session The session for which the SSL handshake should be done
718      * @throws SSLException If the handshake failed
719      */
720     public void initiateHandshake(IoSession session) throws SSLException {
721         IoFilterChain filterChain = session.getFilterChain();
722         
723         if (filterChain == null) {
724             throw new SSLException("No filter chain");
725         }
726         
727         IoFilter.NextFilter nextFilter = filterChain.getNextFilter(SslFilter.class);
728         
729         if (nextFilter == null) {
730             throw new SSLException("No SSL next filter in the chain");
731         }
732         
733         initiateHandshake(nextFilter, session);
734     }
735 
736     private void initiateHandshake(NextFilter nextFilter, IoSession session) throws SSLException {
737         if (LOGGER.isDebugEnabled()) {
738             LOGGER.debug("{} : Starting the first handshake", getSessionInfo(session));
739         }
740         
741         SslHandler sslHandler = getSslSessionHandler(session);
742 
743         try {
744             synchronized (sslHandler) {
745                 sslHandler.handshake(nextFilter);
746                 sslHandler.flushFilterWrite();
747             }
748             sslHandler.flushMessageReceived();
749         } catch (SSLException se) {
750             sslHandler.release();
751             throw se;
752         }
753     }
754 
755     private WriteFuture initiateClosure(NextFilter nextFilter, IoSession session) throws SSLException {
756         SslHandler sslHandler = getSslSessionHandler(session);
757         WriteFuture future = null;
758 
759         // if already shut down
760         try {
761             synchronized(sslHandler) {
762                 if (!sslHandler.closeOutbound()) {
763                     return DefaultWriteFuture.newNotWrittenFuture(session, new IllegalStateException(
764                             "SSL session is shut down already."));
765                 }
766     
767                 // there might be data to write out here?
768                 future = sslHandler.writeNetBuffer(nextFilter);
769     
770                 if (future == null) {
771                     future = DefaultWriteFuture.newWrittenFuture(session);
772                 }
773     
774                 if (sslHandler.isInboundDone()) {
775                     sslHandler.destroy();
776                 }
777             }
778 
779             // Inform that the session is not any more secured
780             session.getFilterChain().fireEvent(SslEvent.UNSECURED);
781         } catch (SSLException se) {
782             sslHandler.release();
783             throw se;
784         }
785 
786         return future;
787     }
788 
789     // Utilities
790     private void handleSslData(NextFilter nextFilter, SslHandler sslHandler) throws SSLException {
791         if (LOGGER.isDebugEnabled()) {
792             LOGGER.debug("{}: Processing the SSL Data ", getSessionInfo(sslHandler.getSession()));
793         }
794 
795         // Flush any buffered write requests occurred before handshaking.
796         if (sslHandler.isHandshakeComplete()) {
797             sslHandler.flushPreHandshakeEvents();
798         }
799 
800         // Write encrypted data to be written (if any)
801         sslHandler.writeNetBuffer(nextFilter);
802 
803         // handle app. data read (if any)
804         handleAppDataRead(nextFilter, sslHandler);
805     }
806 
807     private void handleAppDataRead(NextFilter nextFilter, SslHandler sslHandler) {
808         // forward read app data
809         IoBuffer readBuffer = sslHandler.fetchAppBuffer();
810 
811         if (readBuffer.hasRemaining()) {
812             sslHandler.scheduleMessageReceived(nextFilter, readBuffer);
813         }
814     }
815 
816     private SslHandler getSslSessionHandler(IoSession session) {
817         SslHandler/../../org/apache/mina/filter/ssl/SslHandler.html#SslHandler">SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
818 
819         if (sslHandler == null) {
820             throw new IllegalStateException();
821         }
822 
823         synchronized(sslHandler) {
824             if (sslHandler.getSslFilter() != this) {
825                 throw new IllegalArgumentException("Not managed by this filter.");
826             }
827         }
828 
829         return sslHandler;
830     }
831 
832     /**
833      * A message that is sent from {@link SslFilter} when the connection became
834      * secure or is not secure anymore.
835      *
836      * @author <a href="http://mina.apache.org">Apache MINA Project</a>
837      */
838     public static class SslFilterMessage {
839         private final String name;
840 
841         private SslFilterMessage(String name) {
842             this.name = name;
843         }
844 
845         @Override
846         public String toString() {
847             return name;
848         }
849     }
850     
851     /**
852      * A private class used to store encrypted messages. This is necessary
853      * to be able to emit the messageSent event with the proper original
854      * message, but not for handshake messages, which will be swallowed.
855      *
856      */
857     /* package protected */ static class EncryptedWriteRequest extends DefaultWriteRequest {
858         // Thee encrypted messagee
859         private final IoBuffer encryptedMessage;
860         
861         // The original message
862         private WriteRequest parentRequest;
863 
864         /**
865          * Create a new instance of an EncryptedWriteRequest
866          * @param writeRequest The parent request
867          * @param encryptedMessage The encrypted message
868          */
869         private EncryptedWriteRequest(WriteRequest writeRequest, IoBuffer encryptedMessage) {
870             super(encryptedMessage);
871             parentRequest = writeRequest;
872             this.encryptedMessage = encryptedMessage;
873         }
874 
875         /**
876          * @return teh encrypted message
877          */
878         @Override
879         public Object getMessage() {
880             return encryptedMessage;
881         }
882 
883         /**
884          * @return The parent WriteRequest
885          */
886         public WriteRequest getParentRequest() {
887             return parentRequest;
888         }
889         
890         /**
891          * {@inheritDoc}
892          */
893         @Override
894         public WriteFuture getFuture() {
895             return parentRequest.getFuture();
896         }
897     }
898 }