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