001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.filter.ssl;
021
022import java.net.InetSocketAddress;
023import java.util.ArrayList;
024import java.util.List;
025
026import javax.net.ssl.SSLContext;
027import javax.net.ssl.SSLEngine;
028import javax.net.ssl.SSLException;
029import javax.net.ssl.SSLHandshakeException;
030import javax.net.ssl.SSLSession;
031
032import org.apache.mina.core.buffer.IoBuffer;
033import org.apache.mina.core.filterchain.IoFilter;
034import org.apache.mina.core.filterchain.IoFilterAdapter;
035import org.apache.mina.core.filterchain.IoFilterChain;
036import org.apache.mina.core.future.DefaultWriteFuture;
037import org.apache.mina.core.future.IoFuture;
038import org.apache.mina.core.future.IoFutureListener;
039import org.apache.mina.core.future.WriteFuture;
040import org.apache.mina.core.service.IoAcceptor;
041import org.apache.mina.core.service.IoHandler;
042import org.apache.mina.core.session.AttributeKey;
043import org.apache.mina.core.session.IoSession;
044import org.apache.mina.core.write.WriteRequest;
045import org.apache.mina.core.write.WriteRequestWrapper;
046import org.apache.mina.core.write.WriteToClosedSessionException;
047import org.slf4j.Logger;
048import org.slf4j.LoggerFactory;
049
050/**
051 * An SSL filter that encrypts and decrypts the data exchanged in the session.
052 * Adding this filter triggers SSL handshake procedure immediately by sending
053 * a SSL 'hello' message, so you don't need to call
054 * {@link #startSsl(IoSession)} manually unless you are implementing StartTLS
055 * (see below).  If you don't want the handshake procedure to start
056 * immediately, please specify {@code false} as {@code autoStart} parameter in
057 * the constructor.
058 * <p>
059 * This filter uses an {@link SSLEngine} which was introduced in Java 5, so
060 * Java version 5 or above is mandatory to use this filter. And please note that
061 * this filter only works for TCP/IP connections.
062 *
063 * <h2>Implementing StartTLS</h2>
064 * <p>
065 * You can use {@link #DISABLE_ENCRYPTION_ONCE} attribute to implement StartTLS:
066 * <pre>
067 * public void messageReceived(IoSession session, Object message) {
068 *    if (message instanceof MyStartTLSRequest) {
069 *        // Insert SSLFilter to get ready for handshaking
070 *        session.getFilterChain().addFirst(sslFilter);
071 *
072 *        // Disable encryption temporarily.
073 *        // This attribute will be removed by SSLFilter
074 *        // inside the Session.write() call below.
075 *        session.setAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE);
076 *
077 *        // Write StartTLSResponse which won't be encrypted.
078 *        session.write(new MyStartTLSResponse(OK));
079 *
080 *        // Now DISABLE_ENCRYPTION_ONCE attribute is cleared.
081 *        assert session.getAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE) == null;
082 *    }
083 * }
084 * </pre>
085 *
086 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
087 * @org.apache.xbean.XBean
088 */
089public class SslFilter extends IoFilterAdapter {
090    /** The logger */
091    private static final Logger LOGGER = LoggerFactory.getLogger(SslFilter.class);
092
093    /**
094     * A session attribute key that stores underlying {@link SSLSession}
095     * for each session.
096     */
097    public static final AttributeKey SSL_SESSION = new AttributeKey(SslFilter.class, "session");
098
099    /**
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    private static final boolean START_HANDSHAKE = true;
163
164    private boolean client;
165
166    private boolean needClientAuth;
167
168    private boolean wantClientAuth;
169
170    private String[] enabledCipherSuites;
171
172    private String[] enabledProtocols;
173
174    /**
175     * Creates a new SSL filter using the specified {@link SSLContext}.
176     * The handshake will start immediately.
177     * 
178     * @param sslContext The SSLContext to use
179     */
180    public SslFilter(SSLContext sslContext) {
181        this(sslContext, START_HANDSHAKE);
182    }
183
184    /**
185     * Creates a new SSL filter using the specified {@link SSLContext}.
186     * If the <tt>autostart</tt> flag is set to <tt>true</tt>, the
187     * handshake will start immediately.
188     * 
189     * @param sslContext The SSLContext to use
190     * @param autoStart The flag used to tell the filter to start the handshake immediately
191     */
192    public SslFilter(SSLContext sslContext, boolean autoStart) {
193        if (sslContext == null) {
194            throw new IllegalArgumentException("sslContext");
195        }
196
197        this.sslContext = sslContext;
198        this.autoStart = autoStart;
199    }
200
201    /**
202     * Returns the underlying {@link SSLSession} for the specified session.
203     *
204     * @param session The current session 
205     * @return <tt>null</tt> if no {@link SSLSession} is initialized yet.
206     */
207    public SSLSession getSslSession(IoSession session) {
208        return (SSLSession) session.getAttribute(SSL_SESSION);
209    }
210
211    /**
212     * (Re)starts SSL session for the specified <tt>session</tt> if not started yet.
213     * Please note that SSL session is automatically started by default, and therefore
214     * you don't need to call this method unless you've used TLS closure.
215     *
216     * @param session The session that will be switched to SSL mode
217     * @return <tt>true</tt> if the SSL session has been started, <tt>false</tt> if already started.
218     * @throws SSLException if failed to start the SSL session
219     */
220    public boolean startSsl(IoSession session) throws SSLException {
221        SslHandler sslHandler = getSslSessionHandler(session);
222        boolean started;
223
224        try {
225            synchronized (sslHandler) {
226                if (sslHandler.isOutboundDone()) {
227                    NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER);
228                    sslHandler.destroy();
229                    sslHandler.init();
230                    sslHandler.handshake(nextFilter);
231                    started = true;
232                } else {
233                    started = false;
234                }
235            }
236
237            sslHandler.flushScheduledEvents();
238        } catch (SSLException se) {
239            sslHandler.release();
240            throw se;
241        }
242
243        return started;
244    }
245
246    /**
247     * An extended toString() method for sessions. If the SSL handshake
248     * is not yet completed, we will print (ssl) in small caps. Once it's
249     * completed, we will use SSL capitalized.
250     */
251    /* no qualifier */String getSessionInfo(IoSession session) {
252        StringBuilder sb = new StringBuilder();
253
254        if (session.getService() instanceof IoAcceptor) {
255            sb.append("Session Server");
256
257        } else {
258            sb.append("Session Client");
259        }
260
261        sb.append('[').append(session.getId()).append(']');
262
263        SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
264
265        if (sslHandler == null) {
266            sb.append("(no sslEngine)");
267        } else if (isSslStarted(session)) {
268            if (sslHandler.isHandshakeComplete()) {
269                sb.append("(SSL)");
270            } else {
271                sb.append("(ssl...)");
272            }
273        }
274
275        return sb.toString();
276    }
277
278    /**
279     * @return <tt>true</tt> if and only if the specified <tt>session</tt> is
280     * encrypted/decrypted over SSL/TLS currently. This method will start
281     * to return <tt>false</tt> after TLS <tt>close_notify</tt> message
282     * is sent and any messages written after then is not going to get encrypted.
283     * 
284     * @param session the session we want to check
285     */
286    public boolean isSslStarted(IoSession session) {
287        SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
288
289        if (sslHandler == null) {
290            return false;
291        }
292
293        synchronized (sslHandler) {
294            return !sslHandler.isOutboundDone();
295        }
296    }
297
298    /**
299     * Stops the SSL session by sending TLS <tt>close_notify</tt> message to
300     * initiate TLS closure.
301     *
302     * @param session the {@link IoSession} to initiate TLS closure
303     * @return The Future for the initiated closure
304     * @throws SSLException if failed to initiate TLS closure
305     */
306    public WriteFuture stopSsl(IoSession session) throws SSLException {
307        SslHandler sslHandler = getSslSessionHandler(session);
308        NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER);
309        WriteFuture future;
310
311        try {
312            synchronized (sslHandler) {
313                future = initiateClosure(nextFilter, session);
314            }
315
316            sslHandler.flushScheduledEvents();
317        } catch (SSLException se) {
318            sslHandler.release();
319            throw se;
320        }
321
322        return future;
323    }
324
325    /**
326     * @return <tt>true</tt> if the engine is set to use client mode
327     * when handshaking.
328     */
329    public boolean isUseClientMode() {
330        return client;
331    }
332
333    /**
334     * Configures the engine to use client (or server) mode when handshaking.
335     * 
336     * @param clientMode <tt>true</tt> when we are in client mode, <tt>false</tt> when in server mode
337     */
338    public void setUseClientMode(boolean clientMode) {
339        this.client = clientMode;
340    }
341
342    /**
343     * @return <tt>true</tt> if the engine will <em>require</em> client authentication.
344     * This option is only useful to engines in the server mode.
345     */
346    public boolean isNeedClientAuth() {
347        return needClientAuth;
348    }
349
350    /**
351     * Configures the engine to <em>require</em> client authentication.
352     * This option is only useful for engines in the server mode.
353     * 
354     * @param needClientAuth A flag set when we need to authenticate the client
355     */
356    public void setNeedClientAuth(boolean needClientAuth) {
357        this.needClientAuth = needClientAuth;
358    }
359
360    /**
361     * @return <tt>true</tt> if the engine will <em>request</em> client authentication.
362     * This option is only useful to engines in the server mode.
363     */
364    public boolean isWantClientAuth() {
365        return wantClientAuth;
366    }
367
368    /**
369     * Configures the engine to <em>request</em> client authentication.
370     * This option is only useful for engines in the server mode.
371     * 
372     * @param wantClientAuth A flag set when we want to check the client authentication
373     */
374    public void setWantClientAuth(boolean wantClientAuth) {
375        this.wantClientAuth = wantClientAuth;
376    }
377
378    /**
379     * @return the list of cipher suites to be enabled when {@link SSLEngine}
380     * is initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.'
381     */
382    public String[] getEnabledCipherSuites() {
383        return enabledCipherSuites;
384    }
385
386    /**
387     * Sets the list of cipher suites to be enabled when {@link SSLEngine}
388     * is initialized.
389     *
390     * @param cipherSuites <tt>null</tt> means 'use {@link SSLEngine}'s default.'
391     */
392    public void setEnabledCipherSuites(String[] cipherSuites) {
393        this.enabledCipherSuites = cipherSuites;
394    }
395
396    /**
397     * @return the list of protocols to be enabled when {@link SSLEngine}
398     * is initialized. <tt>null</tt> means 'use {@link SSLEngine}'s default.'
399     */
400    public String[] getEnabledProtocols() {
401        return enabledProtocols;
402    }
403
404    /**
405     * Sets the list of protocols to be enabled when {@link SSLEngine}
406     * is initialized.
407     *
408     * @param protocols <tt>null</tt> means 'use {@link SSLEngine}'s default.'
409     */
410    public void setEnabledProtocols(String[] protocols) {
411        this.enabledProtocols = protocols;
412    }
413
414    /**
415     * Executed just before the filter is added into the chain, we do :
416     * <ul>
417     * <li>check that we don't have a SSL filter already present
418     * <li>we update the next filter
419     * <li>we create the SSL handler helper class
420     * <li>and we store it into the session's Attributes
421     * </ul>
422     */
423    @Override
424    public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
425        // Check that we don't have a SSL filter already present in the chain
426        if (parent.contains(SslFilter.class)) {
427            String msg = "Only one SSL filter is permitted in a chain.";
428            LOGGER.error(msg);
429            throw new IllegalStateException(msg);
430        }
431
432        LOGGER.debug("Adding the SSL Filter {} to the chain", name);
433
434        IoSession session = parent.getSession();
435        session.setAttribute(NEXT_FILTER, nextFilter);
436
437        // Create a SSL handler and start handshake.
438        SslHandler sslHandler = new SslHandler(this, session);
439        
440        // Adding the supported ciphers in the SSLHandler
441        if ((enabledCipherSuites == null) || (enabledCipherSuites.length == 0)) {
442            enabledCipherSuites = sslContext.getServerSocketFactory().getSupportedCipherSuites();
443        }
444
445        sslHandler.init();
446
447        session.setAttribute(SSL_HANDLER, sslHandler);
448    }
449
450    @Override
451    public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
452        if (autoStart == START_HANDSHAKE) {
453            initiateHandshake(nextFilter, parent.getSession());
454        }
455    }
456
457    @Override
458    public void onPreRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
459        IoSession session = parent.getSession();
460        stopSsl(session);
461        session.removeAttribute(NEXT_FILTER);
462        session.removeAttribute(SSL_HANDLER);
463    }
464
465    // IoFilter impl.
466    @Override
467    public void sessionClosed(NextFilter nextFilter, IoSession session) throws SSLException {
468        SslHandler sslHandler = getSslSessionHandler(session);
469
470        try {
471            synchronized (sslHandler) {
472                // release resources
473                sslHandler.destroy();
474            }
475        } finally {
476            // notify closed session
477            nextFilter.sessionClosed(session);
478        }
479    }
480
481    @Override
482    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws SSLException {
483        if (LOGGER.isDebugEnabled()) {
484            LOGGER.debug("{}: Message received : {}", getSessionInfo(session), message);
485        }
486
487        SslHandler sslHandler = getSslSessionHandler(session);
488
489        synchronized (sslHandler) {
490            if (!isSslStarted(session) && sslHandler.isInboundDone()) {
491                // The SSL session must be established first before we
492                // can push data to the application. Store the incoming
493                // data into a queue for a later processing
494                sslHandler.scheduleMessageReceived(nextFilter, message);
495            } else {
496                IoBuffer buf = (IoBuffer) message;
497
498                try {
499                    // forward read encrypted data to SSL handler
500                    sslHandler.messageReceived(nextFilter, buf.buf());
501
502                    // Handle data to be forwarded to application or written to net
503                    handleSslData(nextFilter, sslHandler);
504
505                    if (sslHandler.isInboundDone()) {
506                        if (sslHandler.isOutboundDone()) {
507                            sslHandler.destroy();
508                        } else {
509                            initiateClosure(nextFilter, session);
510                        }
511
512                        if (buf.hasRemaining()) {
513                            // Forward the data received after closure.
514                            sslHandler.scheduleMessageReceived(nextFilter, buf);
515                        }
516                    }
517                } catch (SSLException ssle) {
518                    if (!sslHandler.isHandshakeComplete()) {
519                        SSLException newSsle = new SSLHandshakeException("SSL handshake failed.");
520                        newSsle.initCause(ssle);
521                        ssle = newSsle;
522                        
523                        // Close the session immediately, the handshake has failed
524                        session.closeNow();
525                    } else {
526                        // Free the SSL Handler buffers
527                        sslHandler.release();
528                    }
529
530                    throw ssle;
531                }
532            }
533        }
534
535        sslHandler.flushScheduledEvents();
536    }
537
538    @Override
539    public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) {
540        if (writeRequest instanceof EncryptedWriteRequest) {
541            EncryptedWriteRequest wrappedRequest = (EncryptedWriteRequest) writeRequest;
542            nextFilter.messageSent(session, wrappedRequest.getParentRequest());
543        } else {
544            // ignore extra buffers used for handshaking
545        }
546    }
547
548    @Override
549    public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception {
550
551        if (cause instanceof WriteToClosedSessionException) {
552            // Filter out SSL close notify, which is likely to fail to flush
553            // due to disconnection.
554            WriteToClosedSessionException e = (WriteToClosedSessionException) cause;
555            List<WriteRequest> failedRequests = e.getRequests();
556            boolean containsCloseNotify = false;
557
558            for (WriteRequest r : failedRequests) {
559                if (isCloseNotify(r.getMessage())) {
560                    containsCloseNotify = true;
561                    break;
562                }
563            }
564
565            if (containsCloseNotify) {
566                if (failedRequests.size() == 1) {
567                    // close notify is the only failed request; bail out.
568                    return;
569                }
570
571                List<WriteRequest> newFailedRequests = new ArrayList<WriteRequest>(failedRequests.size() - 1);
572
573                for (WriteRequest r : failedRequests) {
574                    if (!isCloseNotify(r.getMessage())) {
575                        newFailedRequests.add(r);
576                    }
577                }
578
579                if (newFailedRequests.isEmpty()) {
580                    // the failedRequests were full with close notify; bail out.
581                    return;
582                }
583
584                cause = new WriteToClosedSessionException(newFailedRequests, cause.getMessage(), cause.getCause());
585            }
586        }
587
588        nextFilter.exceptionCaught(session, cause);
589    }
590
591    private boolean isCloseNotify(Object message) {
592        if (!(message instanceof IoBuffer)) {
593            return false;
594        }
595
596        IoBuffer buf = (IoBuffer) message;
597        int offset = buf.position();
598
599        return (buf.get(offset + 0) == 0x15) /* Alert */
600                && (buf.get(offset + 1) == 0x03) /* TLS/SSL */
601                && ((buf.get(offset + 2) == 0x00) /* SSL 3.0 */
602                        || (buf.get(offset + 2) == 0x01) /* TLS 1.0 */
603                        || (buf.get(offset + 2) == 0x02) /* TLS 1.1 */
604                        || (buf.get(offset + 2) == 0x03)) /* TLS 1.2 */
605                        && (buf.get(offset + 3) == 0x00); /* close_notify */
606    }
607
608    @Override
609    public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws SSLException {
610        if (LOGGER.isDebugEnabled()) {
611            LOGGER.debug("{}: Writing Message : {}", getSessionInfo(session), writeRequest);
612        }
613
614        boolean needsFlush = true;
615        SslHandler sslHandler = getSslSessionHandler(session);
616
617        try {
618            synchronized (sslHandler) {
619                if (!isSslStarted(session)) {
620                    sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
621                }
622                // Don't encrypt the data if encryption is disabled.
623                else if (session.containsAttribute(DISABLE_ENCRYPTION_ONCE)) {
624                    // Remove the marker attribute because it is temporary.
625                    session.removeAttribute(DISABLE_ENCRYPTION_ONCE);
626                    sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
627                } else {
628                    // Otherwise, encrypt the buffer.
629                    IoBuffer buf = (IoBuffer) writeRequest.getMessage();
630
631                    if (sslHandler.isWritingEncryptedData()) {
632                        // data already encrypted; simply return buffer
633                        sslHandler.scheduleFilterWrite(nextFilter, writeRequest);
634                    } else if (sslHandler.isHandshakeComplete()) {
635                        // SSL encrypt
636                        buf.mark();
637                        sslHandler.encrypt(buf.buf());
638                        IoBuffer encryptedBuffer = sslHandler.fetchOutNetBuffer();
639                        sslHandler.scheduleFilterWrite(nextFilter, new EncryptedWriteRequest(writeRequest,
640                                encryptedBuffer));
641                    } else {
642                        if (session.isConnected()) {
643                            // Handshake not complete yet.
644                            sslHandler.schedulePreHandshakeWriteRequest(nextFilter, writeRequest);
645                        }
646
647                        needsFlush = false;
648                    }
649                }
650            }
651
652            if (needsFlush) {
653                sslHandler.flushScheduledEvents();
654            }
655        } catch (SSLException se) {
656            sslHandler.release();
657            throw se;
658        }
659    }
660
661    @Override
662    public void filterClose(final NextFilter nextFilter, final IoSession session) throws SSLException {
663        SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
664
665        if (sslHandler == null) {
666            // The connection might already have closed, or
667            // SSL might have not started yet.
668            nextFilter.filterClose(session);
669            return;
670        }
671
672        WriteFuture future = null;
673
674        try {
675            synchronized (sslHandler) {
676                if (isSslStarted(session)) {
677                    future = initiateClosure(nextFilter, session);
678                    future.addListener(new IoFutureListener<IoFuture>() {
679                        public void operationComplete(IoFuture future) {
680                            nextFilter.filterClose(session);
681                        }
682                    });
683                }
684            }
685
686            sslHandler.flushScheduledEvents();
687        } catch (SSLException se) {
688            sslHandler.release();
689            throw se;
690        } finally {
691            if (future == null) {
692                nextFilter.filterClose(session);
693            }
694        }
695    }
696
697    /**
698     * Initiate the SSL handshake. This can be invoked if you have set the 'autoStart' to
699     * false when creating the SslFilter instance.
700     * 
701     * @param session The session for which the SSL handshake should be done
702     * @throws SSLException If the handshake failed
703     */
704    public void initiateHandshake(IoSession session) throws SSLException {
705        IoFilterChain filterChain = session.getFilterChain();
706        
707        if (filterChain == null) {
708            throw new SSLException("No filter chain");
709        }
710        
711        IoFilter.NextFilter nextFilter = filterChain.getNextFilter(SslFilter.class);
712        
713        if (nextFilter == null) {
714            throw new SSLException("No SSL next filter in the chain");
715        }
716        
717        initiateHandshake(nextFilter, session);
718    }
719
720    private void initiateHandshake(NextFilter nextFilter, IoSession session) throws SSLException {
721        LOGGER.debug("{} : Starting the first handshake", getSessionInfo(session));
722        SslHandler sslHandler = getSslSessionHandler(session);
723
724        try {
725            synchronized (sslHandler) {
726                sslHandler.handshake(nextFilter);
727            }
728
729            sslHandler.flushScheduledEvents();
730        } catch (SSLException se) {
731            sslHandler.release();
732            throw se;
733        }
734    }
735
736    private WriteFuture initiateClosure(NextFilter nextFilter, IoSession session) throws SSLException {
737        SslHandler sslHandler = getSslSessionHandler(session);
738        WriteFuture future = null;
739
740        // if already shut down
741        try {
742            if (!sslHandler.closeOutbound()) {
743                return DefaultWriteFuture.newNotWrittenFuture(session, new IllegalStateException(
744                        "SSL session is shut down already."));
745            }
746
747            // there might be data to write out here?
748            future = sslHandler.writeNetBuffer(nextFilter);
749
750            if (future == null) {
751                future = DefaultWriteFuture.newWrittenFuture(session);
752            }
753
754            if (sslHandler.isInboundDone()) {
755                sslHandler.destroy();
756            }
757
758            if (session.containsAttribute(USE_NOTIFICATION)) {
759                sslHandler.scheduleMessageReceived(nextFilter, SESSION_UNSECURED);
760            }
761        } catch (SSLException se) {
762            sslHandler.release();
763            throw se;
764        }
765
766        return future;
767    }
768
769    // Utilities
770    private void handleSslData(NextFilter nextFilter, SslHandler sslHandler) throws SSLException {
771        if (LOGGER.isDebugEnabled()) {
772            LOGGER.debug("{}: Processing the SSL Data ", getSessionInfo(sslHandler.getSession()));
773        }
774
775        // Flush any buffered write requests occurred before handshaking.
776        if (sslHandler.isHandshakeComplete()) {
777            sslHandler.flushPreHandshakeEvents();
778        }
779
780        // Write encrypted data to be written (if any)
781        sslHandler.writeNetBuffer(nextFilter);
782
783        // handle app. data read (if any)
784        handleAppDataRead(nextFilter, sslHandler);
785    }
786
787    private void handleAppDataRead(NextFilter nextFilter, SslHandler sslHandler) {
788        // forward read app data
789        IoBuffer readBuffer = sslHandler.fetchAppBuffer();
790
791        if (readBuffer.hasRemaining()) {
792            sslHandler.scheduleMessageReceived(nextFilter, readBuffer);
793        }
794    }
795
796    private SslHandler getSslSessionHandler(IoSession session) {
797        SslHandler sslHandler = (SslHandler) session.getAttribute(SSL_HANDLER);
798
799        if (sslHandler == null) {
800            throw new IllegalStateException();
801        }
802
803        if (sslHandler.getSslFilter() != this) {
804            throw new IllegalArgumentException("Not managed by this filter.");
805        }
806
807        return sslHandler;
808    }
809
810    /**
811     * A message that is sent from {@link SslFilter} when the connection became
812     * secure or is not secure anymore.
813     *
814     * @author <a href="http://mina.apache.org">Apache MINA Project</a>
815     */
816    public static class SslFilterMessage {
817        private final String name;
818
819        private SslFilterMessage(String name) {
820            this.name = name;
821        }
822
823        @Override
824        public String toString() {
825            return name;
826        }
827    }
828
829    private static class EncryptedWriteRequest extends WriteRequestWrapper {
830        private final IoBuffer encryptedMessage;
831
832        private EncryptedWriteRequest(WriteRequest writeRequest, IoBuffer encryptedMessage) {
833            super(writeRequest);
834            this.encryptedMessage = encryptedMessage;
835        }
836
837        @Override
838        public Object getMessage() {
839            return encryptedMessage;
840        }
841    }
842}