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;
21  
22  import javax.net.ssl.SSLContext;
23  import javax.net.ssl.SSLEngine;
24  import javax.net.ssl.SSLException;
25  import javax.net.ssl.SSLHandshakeException;
26  import javax.net.ssl.SSLSession;
27  
28  import org.apache.mina.common.ByteBuffer;
29  import org.apache.mina.common.ByteBufferProxy;
30  import org.apache.mina.common.IoFilterAdapter;
31  import org.apache.mina.common.IoFilterChain;
32  import org.apache.mina.common.IoFuture;
33  import org.apache.mina.common.IoFutureListener;
34  import org.apache.mina.common.IoHandler;
35  import org.apache.mina.common.IoSession;
36  import org.apache.mina.common.WriteFuture;
37  import org.apache.mina.common.support.DefaultWriteFuture;
38  import org.apache.mina.filter.support.SSLHandler;
39  import org.apache.mina.util.SessionLog;
40  
41  /**
42   * An SSL filter that encrypts and decrypts the data exchanged in the session.
43   * Adding this filter triggers SSL handshake procedure immediately by sending
44   * a SSL 'hello' message, so you don't need to call
45   * {@link #startSSL(IoSession)} manually unless you are implementing StartTLS
46   * (see below).
47   * <p>
48   * This filter uses an {@link SSLEngine} which was introduced in Java 5, so 
49   * Java version 5 or above is mandatory to use this filter. And please note that
50   * this filter only works for TCP/IP connections.
51   * <p>
52   * This filter logs debug information using {@link SessionLog}.
53   * 
54   * <h2>Implementing StartTLS</h2>
55   * <p>
56   * You can use {@link #DISABLE_ENCRYPTION_ONCE} attribute to implement StartTLS:
57   * <pre>
58   * public void messageReceived(IoSession session, Object message) {
59   *    if (message instanceof MyStartTLSRequest) {
60   *        // Insert SSLFilter to get ready for handshaking
61   *        session.getFilterChain().addFirst(sslFilter);
62   *
63   *        // Disable encryption temporarilly.
64   *        // This attribute will be removed by SSLFilter
65   *        // inside the Session.write() call below.
66   *        session.setAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE);
67   *
68   *        // Write StartTLSResponse which won't be encrypted.
69   *        session.write(new MyStartTLSResponse(OK));
70   *        
71   *        // Now DISABLE_ENCRYPTION_ONCE attribute is cleared.
72   *        assert session.getAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE) == null;
73   *    }
74   * }
75   * </pre>
76   * 
77   * @author The Apache Directory Project (mina-dev@directory.apache.org)
78   * @version $Rev: 557166 $, $Date: 2007-07-18 15:21:28 +0900 (수, 18  7월 2007) $
79   */
80  public class SSLFilter extends IoFilterAdapter {
81      /**
82       * A session attribute key that stores underlying {@link SSLSession}
83       * for each session.
84       */
85      public static final String SSL_SESSION = SSLFilter.class.getName()
86              + ".SSLSession";
87  
88      /**
89       * A session attribute key that makes next one write request bypass
90       * this filter (not encrypting the data).  This is a marker attribute,
91       * which means that you can put whatever as its value. ({@link Boolean#TRUE}
92       * is preferred.)  The attribute is automatically removed from the session
93       * attribute map as soon as {@link IoSession#write(Object)} is invoked,
94       * and therefore should be put again if you want to make more messages
95       * bypass this filter.  This is especially useful when you implement
96       * StartTLS.   
97       */
98      public static final String DISABLE_ENCRYPTION_ONCE = SSLFilter.class
99              .getName()
100             + ".DisableEncryptionOnce";
101 
102     /**
103      * A session attribute key that makes this filter to emit a
104      * {@link IoHandler#messageReceived(IoSession, Object)} event with a
105      * special message ({@link #SESSION_SECURED} or {@link #SESSION_UNSECURED}).
106      * This is a marker attribute, which means that you can put whatever as its
107      * value. ({@link Boolean#TRUE} is preferred.)  By default, this filter
108      * doesn't emit any events related with SSL session flow control.
109      */
110     public static final String USE_NOTIFICATION = SSLFilter.class.getName()
111             + ".UseNotification";
112 
113     /**
114      * A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)}
115      * event when the session is secured and its {@link #USE_NOTIFICATION}
116      * attribute is set.
117      */
118     public static final SSLFilterMessage SESSION_SECURED = new SSLFilterMessage(
119             "SESSION_SECURED");
120 
121     /**
122      * A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)}
123      * event when the session is not secure anymore and its {@link #USE_NOTIFICATION}
124      * attribute is set.
125      */
126     public static final SSLFilterMessage SESSION_UNSECURED = new SSLFilterMessage(
127             "SESSION_UNSECURED");
128 
129     private static final String NEXT_FILTER = SSLFilter.class.getName()
130             + ".NextFilter";
131 
132     private static final String SSL_HANDLER = SSLFilter.class.getName()
133             + ".SSLHandler";
134 
135     // SSL Context
136     private SSLContext sslContext;
137 
138     private boolean client;
139 
140     private boolean needClientAuth;
141 
142     private boolean wantClientAuth;
143 
144     private String[] enabledCipherSuites;
145 
146     private String[] enabledProtocols;
147 
148     /**
149      * Creates a new SSL filter using the specified {@link SSLContext}.
150      */
151     public SSLFilter(SSLContext sslContext) {
152         if (sslContext == null) {
153             throw new NullPointerException("sslContext");
154         }
155 
156         this.sslContext = sslContext;
157     }
158 
159     /**
160      * Returns the underlying {@link SSLSession} for the specified session.
161      * 
162      * @return <tt>null</tt> if no {@link SSLSession} is initialized yet.
163      */
164     public SSLSession getSSLSession(IoSession session) {
165         return (SSLSession) session.getAttribute(SSL_SESSION);
166     }
167 
168     /**
169      * (Re)starts SSL session for the specified <tt>session</tt> if not started yet.
170      * Please note that SSL session is automatically started by default, and therefore
171      * you don't need to call this method unless you've used TLS closure.
172      * 
173      * @return <tt>true</tt> if the SSL session has been started, <tt>false</tt> if already started.
174      * @throws SSLException if failed to start the SSL session
175      */
176     public boolean startSSL(IoSession session) throws SSLException {
177         SSLHandler handler = getSSLSessionHandler(session);
178         boolean started;
179         synchronized (handler) {
180             if (handler.isOutboundDone()) {
181                 NextFilter nextFilter = (NextFilter) session
182                         .getAttribute(NEXT_FILTER);
183                 handler.destroy();
184                 handler.init();
185                 handler.handshake(nextFilter);
186                 started = true;
187             } else {
188                 started = false;
189             }
190         }
191 
192         handler.flushScheduledEvents();
193         return started;
194     }
195 
196     /**
197      * Returns <tt>true</tt> if and only if the specified <tt>session</tt> is
198      * encrypted/decrypted over SSL/TLS currently.  This method will start
199      * to retun <tt>false</tt> after TLS <tt>close_notify</tt> message
200      * is sent and any messages written after then is not goinf to get encrypted.
201      */
202     public boolean isSSLStarted(IoSession session) {
203         SSLHandler handler = getSSLSessionHandler0(session);
204         if (handler == null) {
205             return false;
206         }
207 
208         synchronized (handler) {
209             return !handler.isOutboundDone();
210         }
211     }
212 
213     /**
214      * Stops the SSL session by sending TLS <tt>close_notify</tt> message to
215      * initiate TLS closure.
216      * 
217      * @param session the {@link IoSession} to initiate TLS closure
218      * @throws SSLException if failed to initiate TLS closure
219      * @throws IllegalArgumentException if this filter is not managing the specified session
220      */
221     public WriteFuture stopSSL(IoSession session) throws SSLException {
222         SSLHandler handler = getSSLSessionHandler(session);
223         NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER);
224         WriteFuture future;
225         synchronized (handler) {
226             future = initiateClosure(nextFilter, session);
227         }
228 
229         handler.flushScheduledEvents();
230 
231         return future;
232     }
233 
234     /**
235      * Returns <tt>true</tt> if the engine is set to use client mode
236      * when handshaking.
237      */
238     public boolean isUseClientMode() {
239         return client;
240     }
241 
242     /**
243      * Configures the engine to use client (or server) mode when handshaking.
244      */
245     public void setUseClientMode(boolean clientMode) {
246         this.client = clientMode;
247     }
248 
249     /**
250      * Returns <tt>true</tt> if the engine will <em>require</em> client authentication.
251      * This option is only useful to engines in the server mode.
252      */
253     public boolean isNeedClientAuth() {
254         return needClientAuth;
255     }
256 
257     /**
258      * Configures the engine to <em>require</em> client authentication.
259      * This option is only useful for engines in the server mode.
260      */
261     public void setNeedClientAuth(boolean needClientAuth) {
262         this.needClientAuth = needClientAuth;
263     }
264 
265     /**
266      * Returns <tt>true</tt> if the engine will <em>request</em> client authentication.
267      * This option is only useful to engines in the server mode.
268      */
269     public boolean isWantClientAuth() {
270         return wantClientAuth;
271     }
272 
273     /**
274      * Configures the engine to <em>request</em> client authentication.
275      * This option is only useful for engines in the server mode.
276      */
277     public void setWantClientAuth(boolean wantClientAuth) {
278         this.wantClientAuth = wantClientAuth;
279     }
280 
281     /**
282      * Returns the list of cipher suites to be enabled when {@link SSLEngine}
283      * is initialized.
284      * 
285      * @return <tt>null</tt> means 'use {@link SSLEngine}'s default.'
286      */
287     public String[] getEnabledCipherSuites() {
288         return enabledCipherSuites;
289     }
290 
291     /**
292      * Sets the list of cipher suites to be enabled when {@link SSLEngine}
293      * is initialized.
294      * 
295      * @param cipherSuites <tt>null</tt> means 'use {@link SSLEngine}'s default.'
296      */
297     public void setEnabledCipherSuites(String[] cipherSuites) {
298         this.enabledCipherSuites = cipherSuites;
299     }
300 
301     /**
302      * Returns the list of protocols to be enabled when {@link SSLEngine}
303      * is initialized.
304      * 
305      * @return <tt>null</tt> means 'use {@link SSLEngine}'s default.'
306      */
307     public String[] getEnabledProtocols() {
308         return enabledProtocols;
309     }
310 
311     /**
312      * Sets the list of protocols to be enabled when {@link SSLEngine}
313      * is initialized.
314      * 
315      * @param protocols <tt>null</tt> means 'use {@link SSLEngine}'s default.'
316      */
317     public void setEnabledProtocols(String[] protocols) {
318         this.enabledProtocols = protocols;
319     }
320 
321     public void onPreAdd(IoFilterChain parent, String name,
322             NextFilter nextFilter) throws SSLException {
323         if (parent.contains(SSLFilter.class)) {
324             throw new IllegalStateException(
325                     "A filter chain cannot contain more than one SSLFilter.");
326         }
327 
328         IoSession session = parent.getSession();
329         session.setAttribute(NEXT_FILTER, nextFilter);
330 
331         // Create an SSL handler and start handshake.
332         SSLHandler handler = new SSLHandler(this, sslContext, session);
333         session.setAttribute(SSL_HANDLER, handler);
334     }
335 
336     public void onPostAdd(IoFilterChain parent, String name,
337             NextFilter nextFilter) throws SSLException {
338         SSLHandler handler = getSSLSessionHandler(parent.getSession());
339         synchronized (handler) {
340             handler.handshake(nextFilter);
341         }
342         handler.flushScheduledEvents();
343     }
344 
345     public void onPreRemove(IoFilterChain parent, String name,
346             NextFilter nextFilter) throws SSLException {
347         IoSession session = parent.getSession();
348         stopSSL(session);
349         session.removeAttribute(NEXT_FILTER);
350         session.removeAttribute(SSL_HANDLER);
351     }
352 
353     // IoFilter impl.
354     public void sessionClosed(NextFilter nextFilter, IoSession session)
355             throws SSLException {
356         SSLHandler handler = getSSLSessionHandler(session);
357         try {
358             synchronized (handler) {
359                 if (isSSLStarted(session)) {
360                     if (SessionLog.isDebugEnabled(session)) {
361                         SessionLog.debug(session, " Closed: "
362                                 + getSSLSessionHandler(session));
363                     }
364                 }
365 
366                 // release resources
367                 handler.destroy();
368             }
369 
370             handler.flushScheduledEvents();
371         } finally {
372             // notify closed session
373             nextFilter.sessionClosed(session);
374         }
375     }
376 
377     public void messageReceived(NextFilter nextFilter, IoSession session,
378             Object message) throws SSLException {
379         SSLHandler handler = getSSLSessionHandler(session);
380         synchronized (handler) {
381             if (!isSSLStarted(session) && handler.isInboundDone()) {
382                 handler.scheduleMessageReceived(nextFilter, message);
383             } else {
384                 ByteBuffer buf = (ByteBuffer) message;
385                 if (SessionLog.isDebugEnabled(session)) {
386                     SessionLog.debug(session, " Data Read: " + handler + " ("
387                             + buf + ')');
388                 }
389 
390                 try {
391                     // forward read encrypted data to SSL handler
392                     handler.messageReceived(nextFilter, buf.buf());
393 
394                     // Handle data to be forwarded to application or written to net
395                     handleSSLData(nextFilter, handler);
396 
397                     if (handler.isInboundDone()) {
398                         if (handler.isOutboundDone()) {
399                             if (SessionLog.isDebugEnabled(session)) {
400                                 SessionLog.debug(session,
401                                         " SSL Session closed.");
402                             }
403 
404                             handler.destroy();
405                         } else {
406                             initiateClosure(nextFilter, session);
407                         }
408 
409                         if (buf.hasRemaining()) {
410                             handler.scheduleMessageReceived(nextFilter,
411                                     buf);
412                         }
413                     }
414                 } catch (SSLException ssle) {
415                     if (!handler.isInitialHandshakeComplete()) {
416                         SSLException newSSLE = new SSLHandshakeException(
417                                 "Initial SSL handshake failed.");
418                         newSSLE.initCause(ssle);
419                         ssle = newSSLE;
420                     }
421 
422                     throw ssle;
423                 }
424             }
425         }
426 
427         handler.flushScheduledEvents();
428     }
429 
430     public void messageSent(NextFilter nextFilter, IoSession session,
431             Object message) {
432         if (message instanceof EncryptedBuffer) {
433             EncryptedBuffer buf = (EncryptedBuffer) message;
434             buf.release();
435             nextFilter.messageSent(session, buf.originalBuffer);
436         } else {
437             // ignore extra buffers used for handshaking
438         }
439     }
440 
441     public void filterWrite(NextFilter nextFilter, IoSession session,
442             WriteRequest writeRequest) throws SSLException {
443         boolean needsFlush = true;
444         SSLHandler handler = getSSLSessionHandler(session);
445         synchronized (handler) {
446             if (!isSSLStarted(session)) {
447                 handler.scheduleFilterWrite(nextFilter,
448                         writeRequest);
449             }
450             // Don't encrypt the data if encryption is disabled.
451             else if (session.containsAttribute(DISABLE_ENCRYPTION_ONCE)) {
452                 // Remove the marker attribute because it is temporary.
453                 session.removeAttribute(DISABLE_ENCRYPTION_ONCE);
454                 handler.scheduleFilterWrite(nextFilter,
455                         writeRequest);
456             } else {
457                 // Otherwise, encrypt the buffer.
458                 ByteBuffer buf = (ByteBuffer) writeRequest.getMessage();
459 
460                 if (SessionLog.isDebugEnabled(session)) {
461                     SessionLog.debug(session, " Filtered Write: " + handler);
462                 }
463 
464                 if (handler.isWritingEncryptedData()) {
465                     // data already encrypted; simply return buffer
466                     if (SessionLog.isDebugEnabled(session)) {
467                         SessionLog.debug(session, "   already encrypted: "
468                                 + buf);
469                     }
470                     handler.scheduleFilterWrite(nextFilter,
471                             writeRequest);
472                 } else if (handler.isInitialHandshakeComplete()) {
473                     // SSL encrypt
474                     if (SessionLog.isDebugEnabled(session)) {
475                         SessionLog.debug(session, " encrypt: " + buf);
476                     }
477 
478                     int pos = buf.position();
479                     handler.encrypt(buf.buf());
480                     buf.position(pos);
481                     ByteBuffer encryptedBuffer = new EncryptedBuffer(SSLHandler
482                             .copy(handler.getOutNetBuffer()), buf);
483 
484                     if (SessionLog.isDebugEnabled(session)) {
485                         SessionLog.debug(session, " encrypted buf: "
486                                 + encryptedBuffer);
487                     }
488                     handler.scheduleFilterWrite(nextFilter,
489                             new WriteRequest(encryptedBuffer, writeRequest
490                                     .getFuture()));
491                 } else {
492                     if (!session.isConnected()) {
493                         if (SessionLog.isDebugEnabled(session)) {
494                             SessionLog.debug(session,
495                                     " Write request on closed session.");
496                         }
497                     } else {
498                         if (SessionLog.isDebugEnabled(session)) {
499                             SessionLog
500                                     .debug(session,
501                                             " Handshaking is not complete yet. Buffering write request.");
502                         }
503                         handler.schedulePreHandshakeWriteRequest(nextFilter,
504                                 writeRequest);
505                     }
506                     needsFlush = false;
507                 }
508             }
509         }
510 
511         if (needsFlush) {
512             handler.flushScheduledEvents();
513         }
514     }
515 
516     public void filterClose(final NextFilter nextFilter, final IoSession session)
517             throws SSLException {
518         SSLHandler handler = getSSLSessionHandler0(session);
519         if (handler == null) {
520             // The connection might already have closed, or
521             // SSL might have not started yet.
522             nextFilter.filterClose(session);
523             return;
524         }
525 
526         WriteFuture future = null;
527         try {
528             synchronized (handler) {
529                 if (isSSLStarted(session)) {
530                     future = initiateClosure(nextFilter, session);
531                 }
532             }
533 
534             handler.flushScheduledEvents();
535         } finally {
536             if (future == null) {
537                 nextFilter.filterClose(session);
538             } else {
539                 future.addListener(new IoFutureListener() {
540                     public void operationComplete(IoFuture future) {
541                         nextFilter.filterClose(session);
542                     }
543                 });
544             }
545         }
546     }
547 
548     private WriteFuture initiateClosure(NextFilter nextFilter, IoSession session)
549             throws SSLException {
550         SSLHandler handler = getSSLSessionHandler(session);
551         // if already shut down
552         if (!handler.closeOutbound()) {
553             return DefaultWriteFuture.newNotWrittenFuture(session);
554         }
555 
556         // there might be data to write out here?
557         WriteFuture future = handler.writeNetBuffer(nextFilter);
558 
559         if (handler.isInboundDone()) {
560             handler.destroy();
561         }
562 
563         if (session.containsAttribute(USE_NOTIFICATION)) {
564             handler.scheduleMessageReceived(nextFilter, SESSION_UNSECURED);
565         }
566 
567         return future;
568     }
569 
570     // Utiliities
571 
572     private void handleSSLData(NextFilter nextFilter, SSLHandler handler)
573             throws SSLException {
574         // Flush any buffered write requests occurred before handshaking.
575         if (handler.isInitialHandshakeComplete()) {
576             handler.flushPreHandshakeEvents();
577         }
578 
579         // Write encrypted data to be written (if any)
580         handler.writeNetBuffer(nextFilter);
581 
582         // handle app. data read (if any)
583         handleAppDataRead(nextFilter, handler);
584     }
585 
586     private void handleAppDataRead(NextFilter nextFilter, SSLHandler handler) {
587         IoSession session = handler.getSession();
588         if (!handler.getAppBuffer().hasRemaining()) {
589             return;
590         }
591 
592         if (SessionLog.isDebugEnabled(session)) {
593             SessionLog.debug(session, " appBuffer: " + handler.getAppBuffer());
594         }
595 
596         // forward read app data
597         ByteBuffer readBuffer = SSLHandler.copy(handler.getAppBuffer());
598         if (SessionLog.isDebugEnabled(session)) {
599             SessionLog.debug(session, " app data read: " + readBuffer + " ("
600                     + readBuffer.getHexDump() + ')');
601         }
602 
603         handler.scheduleMessageReceived(nextFilter, readBuffer);
604     }
605 
606     private SSLHandler getSSLSessionHandler(IoSession session) {
607         SSLHandler handler = getSSLSessionHandler0(session);
608         if (handler == null) {
609             throw new IllegalStateException();
610         }
611         if (handler.getParent() != this) {
612             throw new IllegalArgumentException("Not managed by this filter.");
613         }
614         return handler;
615     }
616 
617     private SSLHandler getSSLSessionHandler0(IoSession session) {
618         return (SSLHandler) session.getAttribute(SSL_HANDLER);
619     }
620 
621     /**
622      * A message that is sent from {@link SSLFilter} when the connection became
623      * secure or is not secure anymore. 
624      *
625      * @author The Apache Directory Project (mina-dev@directory.apache.org)
626      * @version $Rev: 557166 $, $Date: 2007-07-18 15:21:28 +0900 (수, 18  7월 2007) $
627      */
628     public static class SSLFilterMessage {
629         private final String name;
630 
631         private SSLFilterMessage(String name) {
632             this.name = name;
633         }
634 
635         public String toString() {
636             return name;
637         }
638     }
639 
640     private static class EncryptedBuffer extends ByteBufferProxy {
641         private final ByteBuffer originalBuffer;
642 
643         private EncryptedBuffer(ByteBuffer buf, ByteBuffer originalBuffer) {
644             super(buf);
645             this.originalBuffer = originalBuffer;
646         }
647     }
648 }