KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > mina > filter > SSLFilter


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: 557169 $, $Date: 2007-07-18 15:26:04 +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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc NEXT_FILTER = SSLFilter.class.getName()
130             + ".NextFilter";
131
132     private static final String JavaDoc 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 JavaDoc[] enabledCipherSuites;
145
146     private String JavaDoc[] 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 JavaDoc("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 JavaDoc[] 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 JavaDoc[] 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 JavaDoc[] 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 JavaDoc[] protocols) {
318         this.enabledProtocols = protocols;
319     }
320
321     public void onPreAdd(IoFilterChain parent, String JavaDoc name,
322             NextFilter nextFilter) throws SSLException {
323         if (parent.contains(SSLFilter.class)) {
324             throw new IllegalStateException JavaDoc(
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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc();
610         }
611         if (handler.getParent() != this) {
612             throw new IllegalArgumentException JavaDoc("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: 557169 $, $Date: 2007-07-18 15:26:04 +0900 (수, 18 7월 2007) $
627      */

628     public static class SSLFilterMessage {
629         private final String JavaDoc name;
630
631         private SSLFilterMessage(String JavaDoc name) {
632             this.name = name;
633         }
634
635         public String JavaDoc 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 }
649
Popular Tags