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