View Javadoc

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