001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.apache.mina.filter.ssl;
021:
022: import java.net.InetSocketAddress;
023: import java.util.ArrayList;
024: import java.util.List;
025:
026: import javax.net.ssl.SSLContext;
027: import javax.net.ssl.SSLEngine;
028: import javax.net.ssl.SSLException;
029: import javax.net.ssl.SSLHandshakeException;
030: import javax.net.ssl.SSLSession;
031:
032: import org.apache.mina.common.AttributeKey;
033: import org.apache.mina.common.DefaultWriteFuture;
034: import org.apache.mina.common.IoBuffer;
035: import org.apache.mina.common.IoFilterAdapter;
036: import org.apache.mina.common.IoFilterChain;
037: import org.apache.mina.common.IoFuture;
038: import org.apache.mina.common.IoFutureListener;
039: import org.apache.mina.common.IoHandler;
040: import org.apache.mina.common.IoSession;
041: import org.apache.mina.common.WriteFuture;
042: import org.apache.mina.common.WriteRequest;
043: import org.apache.mina.common.WriteRequestWrapper;
044: import org.apache.mina.common.WriteToClosedSessionException;
045:
046: /**
047: * An SSL filter that encrypts and decrypts the data exchanged in the session.
048: * Adding this filter triggers SSL handshake procedure immediately by sending
049: * a SSL 'hello' message, so you don't need to call
050: * {@link #startSsl(IoSession)} manually unless you are implementing StartTLS
051: * (see below). If you don't want the handshake procedure to start
052: * immediately, please specify {@code true} as {@code autoStart} parameter in
053: * the constructor.
054: * <p>
055: * This filter uses an {@link SSLEngine} which was introduced in Java 5, so
056: * Java version 5 or above is mandatory to use this filter. And please note that
057: * this filter only works for TCP/IP connections.
058: * <p>
059: *
060: * <h2>Implementing StartTLS</h2>
061: * <p>
062: * You can use {@link #DISABLE_ENCRYPTION_ONCE} attribute to implement StartTLS:
063: * <pre>
064: * public void messageReceived(IoSession session, Object message) {
065: * if (message instanceof MyStartTLSRequest) {
066: * // Insert SSLFilter to get ready for handshaking
067: * session.getFilterChain().addFirst(sslFilter);
068: *
069: * // Disable encryption temporarilly.
070: * // This attribute will be removed by SSLFilter
071: * // inside the Session.write() call below.
072: * session.setAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE);
073: *
074: * // Write StartTLSResponse which won't be encrypted.
075: * session.write(new MyStartTLSResponse(OK));
076: *
077: * // Now DISABLE_ENCRYPTION_ONCE attribute is cleared.
078: * assert session.getAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE) == null;
079: * }
080: * }
081: * </pre>
082: *
083: * @author The Apache MINA Project (dev@mina.apache.org)
084: * @version $Rev: 616100 $, $Date: 2008-01-28 15:58:32 -0700 (Mon, 28 Jan 2008) $
085: */
086: public class SslFilter extends IoFilterAdapter {
087: /**
088: * A session attribute key that stores underlying {@link SSLSession}
089: * for each session.
090: */
091: public static final AttributeKey SSL_SESSION = new AttributeKey(
092: SslFilter.class, "session");
093:
094: /**
095: * A session attribute key that makes next one write request bypass
096: * this filter (not encrypting the data). This is a marker attribute,
097: * which means that you can put whatever as its value. ({@link Boolean#TRUE}
098: * is preferred.) The attribute is automatically removed from the session
099: * attribute map as soon as {@link IoSession#write(Object)} is invoked,
100: * and therefore should be put again if you want to make more messages
101: * bypass this filter. This is especially useful when you implement
102: * StartTLS.
103: */
104: public static final AttributeKey DISABLE_ENCRYPTION_ONCE = new AttributeKey(
105: SslFilter.class, "disableOnce");
106:
107: /**
108: * A session attribute key that makes this filter to emit a
109: * {@link IoHandler#messageReceived(IoSession, Object)} event with a
110: * special message ({@link #SESSION_SECURED} or {@link #SESSION_UNSECURED}).
111: * This is a marker attribute, which means that you can put whatever as its
112: * value. ({@link Boolean#TRUE} is preferred.) By default, this filter
113: * doesn't emit any events related with SSL session flow control.
114: */
115: public static final AttributeKey USE_NOTIFICATION = new AttributeKey(
116: SslFilter.class, "useNotification");
117:
118: /**
119: * A session attribute key that should be set to an {@link InetSocketAddress}.
120: * Setting this attribute causes
121: * {@link SSLContext#createSSLEngine(String, int)} to be called passing the
122: * hostname and port of the {@link InetSocketAddress} to get an
123: * {@link SSLEngine} instance. If not set {@link SSLContext#createSSLEngine()}
124: * will be called.
125: * .
126: * Using this feature {@link SSLSession} objects may be cached and reused
127: * when in client mode.
128: *
129: * @see SSLContext#createSSLEngine(String, int)
130: */
131: public static final AttributeKey PEER_ADDRESS = new AttributeKey(
132: SslFilter.class, "peerAddress");
133:
134: /**
135: * A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)}
136: * event when the session is secured and its {@link #USE_NOTIFICATION}
137: * attribute is set.
138: */
139: public static final SslFilterMessage SESSION_SECURED = new SslFilterMessage(
140: "SESSION_SECURED");
141:
142: /**
143: * A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)}
144: * event when the session is not secure anymore and its {@link #USE_NOTIFICATION}
145: * attribute is set.
146: */
147: public static final SslFilterMessage SESSION_UNSECURED = new SslFilterMessage(
148: "SESSION_UNSECURED");
149:
150: private static final AttributeKey NEXT_FILTER = new AttributeKey(
151: SslFilter.class, "nextFilter");
152: private static final AttributeKey SSL_HANDLER = new AttributeKey(
153: SslFilter.class, "handler");
154:
155: // SSL Context
156: private final SSLContext sslContext;
157:
158: private final boolean autoStart;
159:
160: private boolean client;
161:
162: private boolean needClientAuth;
163:
164: private boolean wantClientAuth;
165:
166: private String[] enabledCipherSuites;
167:
168: private String[] enabledProtocols;
169:
170: /**
171: * Creates a new SSL filter using the specified {@link SSLContext}.
172: */
173: public SslFilter(SSLContext sslContext) {
174: this (sslContext, true);
175: }
176:
177: /**
178: * Creates a new SSL filter using the specified {@link SSLContext}.
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 = getSslSessionHandler0(session);
234: if (handler == null) {
235: return false;
236: }
237:
238: synchronized (handler) {
239: return !handler.isOutboundDone();
240: }
241: }
242:
243: /**
244: * Stops the SSL session by sending TLS <tt>close_notify</tt> message to
245: * initiate TLS closure.
246: *
247: * @param session the {@link IoSession} to initiate TLS closure
248: * @throws SSLException if failed to initiate TLS closure
249: * @throws IllegalArgumentException if this filter is not managing the specified session
250: */
251: public WriteFuture stopSsl(IoSession session) throws SSLException {
252: SslHandler handler = getSslSessionHandler(session);
253: NextFilter nextFilter = (NextFilter) session
254: .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("Only one "
357: + 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,
405: IoSession session, 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,
429: buf);
430: }
431: }
432: } catch (SSLException ssle) {
433: if (!handler.isHandshakeComplete()) {
434: SSLException newSsle = new SSLHandshakeException(
435: "SSL handshake failed.");
436: newSsle.initCause(ssle);
437: ssle = newSsle;
438: }
439:
440: throw ssle;
441: }
442: }
443: }
444:
445: handler.flushScheduledEvents();
446: }
447:
448: @Override
449: public void messageSent(NextFilter nextFilter, IoSession session,
450: WriteRequest writeRequest) {
451: if (writeRequest instanceof EncryptedWriteRequest) {
452: EncryptedWriteRequest wrappedRequest = (EncryptedWriteRequest) writeRequest;
453: nextFilter.messageSent(session, wrappedRequest
454: .getParentRequest());
455: } else {
456: // ignore extra buffers used for handshaking
457: }
458: }
459:
460: @Override
461: public void exceptionCaught(NextFilter nextFilter,
462: IoSession session, Throwable cause) throws Exception {
463:
464: if (cause instanceof WriteToClosedSessionException) {
465: // Filter out SSL close notify, which is likely to fail to flush
466: // due to disconnection.
467: WriteToClosedSessionException e = (WriteToClosedSessionException) cause;
468: List<WriteRequest> failedRequests = e.getRequests();
469: boolean containsCloseNotify = false;
470: for (WriteRequest r : failedRequests) {
471: if (isCloseNotify(r.getMessage())) {
472: containsCloseNotify = true;
473: break;
474: }
475: }
476:
477: if (containsCloseNotify) {
478: if (failedRequests.size() == 1) {
479: // close notify is the only failed request; bail out.
480: return;
481: }
482:
483: List<WriteRequest> newFailedRequests = new ArrayList<WriteRequest>(
484: failedRequests.size() - 1);
485: for (WriteRequest r : failedRequests) {
486: if (!isCloseNotify(r.getMessage())) {
487: newFailedRequests.add(r);
488: }
489: }
490:
491: if (newFailedRequests.isEmpty()) {
492: // the failedRequests were full with close notify; bail out.
493: return;
494: }
495:
496: cause = new WriteToClosedSessionException(
497: newFailedRequests, cause.getMessage(), cause
498: .getCause());
499: }
500: }
501:
502: nextFilter.exceptionCaught(session, cause);
503: }
504:
505: private boolean isCloseNotify(Object message) {
506: if (!(message instanceof IoBuffer)) {
507: return false;
508: }
509:
510: IoBuffer buf = (IoBuffer) message;
511: int offset = buf.position();
512: return buf.remaining() == 23 && buf.get(offset + 0) == 0x15
513: && buf.get(offset + 1) == 0x03
514: && buf.get(offset + 2) == 0x01
515: && buf.get(offset + 3) == 0x00
516: && buf.get(offset + 4) == 0x12;
517: }
518:
519: @Override
520: public void filterWrite(NextFilter nextFilter, IoSession session,
521: WriteRequest writeRequest) throws SSLException {
522: boolean needsFlush = true;
523: SslHandler handler = getSslSessionHandler(session);
524: synchronized (handler) {
525: if (!isSslStarted(session)) {
526: handler.scheduleFilterWrite(nextFilter, writeRequest);
527: }
528: // Don't encrypt the data if encryption is disabled.
529: else if (session.containsAttribute(DISABLE_ENCRYPTION_ONCE)) {
530: // Remove the marker attribute because it is temporary.
531: session.removeAttribute(DISABLE_ENCRYPTION_ONCE);
532: handler.scheduleFilterWrite(nextFilter, writeRequest);
533: } else {
534: // Otherwise, encrypt the buffer.
535: IoBuffer buf = (IoBuffer) writeRequest.getMessage();
536:
537: if (handler.isWritingEncryptedData()) {
538: // data already encrypted; simply return buffer
539: handler.scheduleFilterWrite(nextFilter,
540: writeRequest);
541: } else if (handler.isHandshakeComplete()) {
542: // SSL encrypt
543: int pos = buf.position();
544: handler.encrypt(buf.buf());
545: buf.position(pos);
546: IoBuffer encryptedBuffer = handler
547: .fetchOutNetBuffer();
548: handler.scheduleFilterWrite(nextFilter,
549: new EncryptedWriteRequest(writeRequest,
550: encryptedBuffer));
551: } else {
552: if (session.isConnected()) {
553: // Handshake not complete yet.
554: handler.schedulePreHandshakeWriteRequest(
555: nextFilter, writeRequest);
556: }
557: needsFlush = false;
558: }
559: }
560: }
561:
562: if (needsFlush) {
563: handler.flushScheduledEvents();
564: }
565: }
566:
567: @Override
568: public void filterClose(final NextFilter nextFilter,
569: final IoSession session) throws SSLException {
570: SslHandler handler = getSslSessionHandler0(session);
571: if (handler == null) {
572: // The connection might already have closed, or
573: // SSL might have not started yet.
574: nextFilter.filterClose(session);
575: return;
576: }
577:
578: WriteFuture future = null;
579: try {
580: synchronized (handler) {
581: if (isSslStarted(session)) {
582: future = initiateClosure(nextFilter, session);
583: future
584: .addListener(new IoFutureListener<IoFuture>() {
585: public void operationComplete(
586: IoFuture future) {
587: nextFilter.filterClose(session);
588: }
589: });
590: }
591: }
592:
593: handler.flushScheduledEvents();
594: } finally {
595: if (future == null) {
596: nextFilter.filterClose(session);
597: }
598: }
599: }
600:
601: private void initiateHandshake(NextFilter nextFilter,
602: IoSession session) throws SSLException {
603: SslHandler handler = getSslSessionHandler(session);
604: synchronized (handler) {
605: handler.handshake(nextFilter);
606: }
607: handler.flushScheduledEvents();
608: }
609:
610: private WriteFuture initiateClosure(NextFilter nextFilter,
611: IoSession session) throws SSLException {
612: SslHandler handler = getSslSessionHandler(session);
613: // if already shut down
614: if (!handler.closeOutbound()) {
615: return DefaultWriteFuture.newNotWrittenFuture(session,
616: new IllegalStateException(
617: "SSL session is shut down already."));
618: }
619:
620: // there might be data to write out here?
621: WriteFuture future = handler.writeNetBuffer(nextFilter);
622: if (future == null) {
623: future = DefaultWriteFuture.newWrittenFuture(session);
624: }
625:
626: if (handler.isInboundDone()) {
627: handler.destroy();
628: }
629:
630: if (session.containsAttribute(USE_NOTIFICATION)) {
631: handler.scheduleMessageReceived(nextFilter,
632: SESSION_UNSECURED);
633: }
634:
635: return future;
636: }
637:
638: // Utiliities
639:
640: private void handleSslData(NextFilter nextFilter, SslHandler handler)
641: throws SSLException {
642: // Flush any buffered write requests occurred before handshaking.
643: if (handler.isHandshakeComplete()) {
644: handler.flushPreHandshakeEvents();
645: }
646:
647: // Write encrypted data to be written (if any)
648: handler.writeNetBuffer(nextFilter);
649:
650: // handle app. data read (if any)
651: handleAppDataRead(nextFilter, handler);
652: }
653:
654: private void handleAppDataRead(NextFilter nextFilter,
655: SslHandler handler) {
656: // forward read app data
657: IoBuffer readBuffer = handler.fetchAppBuffer();
658: if (readBuffer.hasRemaining()) {
659: handler.scheduleMessageReceived(nextFilter, readBuffer);
660: }
661: }
662:
663: private SslHandler getSslSessionHandler(IoSession session) {
664: SslHandler handler = getSslSessionHandler0(session);
665: if (handler == null) {
666: throw new IllegalStateException();
667: }
668: if (handler.getParent() != this ) {
669: throw new IllegalArgumentException(
670: "Not managed by this filter.");
671: }
672: return handler;
673: }
674:
675: private SslHandler getSslSessionHandler0(IoSession session) {
676: return (SslHandler) session.getAttribute(SSL_HANDLER);
677: }
678:
679: /**
680: * A message that is sent from {@link SslFilter} when the connection became
681: * secure or is not secure anymore.
682: *
683: * @author The Apache MINA Project (dev@mina.apache.org)
684: * @version $Rev: 616100 $, $Date: 2008-01-28 15:58:32 -0700 (Mon, 28 Jan 2008) $
685: */
686: public static class SslFilterMessage {
687: private final String name;
688:
689: private SslFilterMessage(String name) {
690: this .name = name;
691: }
692:
693: @Override
694: public String toString() {
695: return name;
696: }
697: }
698:
699: private static class EncryptedWriteRequest extends
700: WriteRequestWrapper {
701: private final IoBuffer encryptedMessage;
702:
703: private EncryptedWriteRequest(WriteRequest writeRequest,
704: IoBuffer encryptedMessage) {
705: super (writeRequest);
706: this .encryptedMessage = encryptedMessage;
707: }
708:
709: @Override
710: public Object getMessage() {
711: return encryptedMessage;
712: }
713: }
714: }
|