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.transport.nio;
    21
    22  import static org.apache.mina.session.AttributeKey.createKey;
    23
    24  import java.net.InetSocketAddress;
    25  import java.nio.ByteBuffer;
    26  import java.util.Queue;
    27
    28  import javax.net.ssl.SSLContext;
    29  import javax.net.ssl.SSLEngine;
    30  import javax.net.ssl.SSLEngineResult;
    31  import javax.net.ssl.SSLEngineResult.HandshakeStatus;
    32  import javax.net.ssl.SSLException;
    33  import javax.net.ssl.SSLSession;
    34
    35  import org.apache.mina.api.IoClient;
    36  import org.apache.mina.api.IoSession;
    37  import org.apache.mina.session.AbstractIoSession;
    38  import org.apache.mina.session.AttributeKey;
    39  import org.apache.mina.session.DefaultWriteRequest;
    40  import org.apache.mina.session.WriteRequest;
    41  import org.slf4j.Logger;
    42  import org.slf4j.LoggerFactory;
    43
    44  /**
    45   * An helper class used to manage everything related to SSL/TLS establishment and management.
    46   *
    47   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    48   */
    49  public class SslHelper {
    50      /** A logger for this class */
    51      private static final Logger LOGGER = LoggerFactory.getLogger(SslHelper.class);
    52
    53      /** The SSL engine instance */
    54      private SSLEngine sslEngine;
    55
    56      /** The SSLContext instance */
    57      private final SSLContext sslContext;
    58
    59      /** The current session */
    60      private final IoSession session;
    61
    62      /**
    63       * A session attribute key that should be set to an {@link InetSocketAddress}. Setting this attribute causes
    64       * {@link SSLContext#createSSLEngine(String, int)} to be called passing the hostname and port of the
    65       * {@link InetSocketAddress} to get an {@link SSLEngine} instance. If not set {@link SSLContext#createSSLEngine()}
    66       * will be called.<br/>
    67       * Using this feature {@link SSLSession} objects may be cached and reused when in client mode.
    68       *
    69       * @see SSLContext#createSSLEngine(String, int)
    70       */
    71      public static final AttributeKey<InetSocketAddress> PEER_ADDRESS = createKey(InetSocketAddress.class,
    72              "internal_peerAddress");
    73
    74      public static final AttributeKey<Boolean> WANT_CLIENT_AUTH = createKey(Boolean.class, "internal_wantClientAuth");
    75
    76      public static final AttributeKey<Boolean> NEED_CLIENT_AUTH = createKey(Boolean.class, "internal_needClientAuth");
    77
    78      /** Incoming buffer accumulating bytes read from the channel */
    79      /** An empty buffer used during the handshake phase */
    80      private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
    81
    82      private ByteBuffer previous = null;
    83
    84      /**
    85       * Create a new SSL Handler.
    86       *
    87       * @param session The associated session
    88       */
    89      public SslHelper(IoSession session, SSLContext sslContext) {
    90          this.session = session;
    91          this.sslContext = sslContext;
    92      }
    93
    94      /**
    95       * @return The associated session
    96       */
    97      /* no qualifier */IoSession getSession() {
    98          return session;
    99      }
    100
    101     /**
    102      * @return The associated SSLEngine
    103      */
    104     /* no qualifier */SSLEngine getEngine() {
    105         return sslEngine;
    106     }
    107
    108     boolean isHanshaking() {
    109         return sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING;
    110     }
    111
    112     /**
    113      * Initialize the SSL handshake.
    114      *
    115      */
    116     public void init() {
    117         if (sslEngine != null) {
    118             // We already have a SSL engine created, no need to create a new one
    119             return;
    120         }
    121
    122         LOGGER.debug("{} Initializing the SSL Helper", session);
    123
    124         InetSocketAddress peer = session.getAttribute(PEER_ADDRESS, null);
    125
    126         // Create the SSL engine here
    127         if (peer == null) {
    128             sslEngine = sslContext.createSSLEngine();
    129         } else {
    130             sslEngine = sslContext.createSSLEngine(peer.getHostName(), peer.getPort());
    131         }
    132
    133         // Initialize the engine in client mode if necessary
    134         sslEngine.setUseClientMode(session.getService() instanceof IoClient);
    135
    136         // Initialize the different SslEngine modes
    137         if (!sslEngine.getUseClientMode()) {
    138             // Those parameters are only valid when in server mode
    139             boolean needClientAuth = session.getAttribute(NEED_CLIENT_AUTH, false);
    140             boolean wantClientAuth = session.getAttribute(WANT_CLIENT_AUTH, false);
    141
    142             // The WantClientAuth supersede the NeedClientAuth, if set.
    143             if (needClientAuth) {
    144                 sslEngine.setNeedClientAuth(true);
    145             }
    146
    147             if (wantClientAuth) {
    148                 sslEngine.setWantClientAuth(true);
    149             }
    150         }
    151
    152         if (LOGGER.isDebugEnabled()) {
    153             LOGGER.debug("{} SSL Handler Initialization done.", session);
    154         }
    155     }
    156
    157     /**
    158      * Duplicate a byte buffer for storing it into this context for future use.
    159      *
    160      * @param buffer the buffer to duplicate
    161      * @return the newly allocated buffer
    162      */
    163     private ByteBuffer duplicate(ByteBuffer buffer) {
    164         ByteBuffer newBuffer = ByteBuffer.allocateDirect(buffer.remaining() * 2);
    165         newBuffer.put(buffer);
    166         newBuffer.flip();
    167         return newBuffer;
    168     }
    169
    170     /**
    171      * Accumulate the given buffer into the current context. Allocation is performed only if needed.
    172      *
    173      * @param buffer the buffer to accumulate
    174      * @return the accumulated buffer
    175      */
    176     private ByteBuffer accumulate(ByteBuffer buffer) {
    177         if (previous.capacity() - previous.remaining() > buffer.remaining()) {
    178             int oldPosition = previous.position();
    179             previous.position(previous.limit());
    180             previous.limit(previous.limit() + buffer.remaining());
    181             previous.put(buffer);
    182             previous.position(oldPosition);
    183         } else {
    184             ByteBuffer newPrevious = ByteBuffer.allocateDirect((previous.remaining() + buffer.remaining()) * 2);
    185             newPrevious.put(previous);
    186             newPrevious.put(buffer);
    187             newPrevious.flip();
    188             previous = newPrevious;
    189         }
    190         return previous;
    191     }
    192
    193     /**
    194      * Process a read ByteBuffer over a secured connection, or during the SSL/TLS Handshake.
    195      *
    196      * @param session The session we are processing a read for
    197      * @param readBuffer The data we get from the channel
    198      * @throws SSLException If the unwrapping or handshaking failed
    199      */
    200     public void processRead(AbstractIoSession session, ByteBuffer readBuffer) throws SSLException {
    201         ByteBuffer tempBuffer;
    202
    203         if (previous != null) {
    204             tempBuffer = accumulate(readBuffer);
    205         } else {
    206             tempBuffer = readBuffer;
    207         }
    208
    209         boolean done = false;
    210         SSLEngineResult result;
    211         ByteBuffer appBuffer = ByteBuffer.allocateDirect(sslEngine.getSession().getApplicationBufferSize());
    212
    213         HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
    214         while (!done) {
    215             switch (handshakeStatus) {
    216             case NEED_UNWRAP:
    217             case NOT_HANDSHAKING:
    218             case FINISHED:
    219                 result = sslEngine.unwrap(tempBuffer, appBuffer);
    220                 handshakeStatus = result.getHandshakeStatus();
    221
    222                 switch (result.getStatus()) {
    223                 case BUFFER_UNDERFLOW:
    224                     /* we need more data */
    225                     done = true;
    226                     break;
    227                 case BUFFER_OVERFLOW:
    228                     /* resize output buffer */
    229                     appBuffer = ByteBuffer.allocateDirect(appBuffer.capacity() * 2);
    230                     break;
    231                 case OK:
    232                     if ((handshakeStatus == HandshakeStatus.NOT_HANDSHAKING) && (result.bytesProduced() > 0)) {
    233                         appBuffer.flip();
    234                         session.processMessageReceived(appBuffer);
    235                     }
    236                 }
    237                 break;
    238             case NEED_TASK:
    239                 Runnable task;
    240
    241                 while ((task = sslEngine.getDelegatedTask()) != null) {
    242                     task.run();
    243                 }
    244                 handshakeStatus = sslEngine.getHandshakeStatus();
    245                 break;
    246             case NEED_WRAP:
    247                 result = sslEngine.wrap(EMPTY_BUFFER, appBuffer);
    248                 handshakeStatus = result.getHandshakeStatus();
    249                 switch (result.getStatus()) {
    250                 case BUFFER_OVERFLOW:
    251                     appBuffer = ByteBuffer.allocateDirect(appBuffer.capacity() * 2);
    252                     break;
    253                 case BUFFER_UNDERFLOW:
    254                     done = true;
    255                     break;
    256                 case CLOSED:
    257                 case OK:
    258                     appBuffer.flip();
    259                     WriteRequest writeRequest = new DefaultWriteRequest(readBuffer);
    260                     writeRequest.setMessage(appBuffer);
    261                     session.enqueueWriteRequest(writeRequest);
    262                     break;
    263                 }
    264             }
    265         }
    266         if (tempBuffer.remaining() > 0) {
    267             previous = duplicate(tempBuffer);
    268         } else {
    269             previous = null;
    270         }
    271         readBuffer.clear();
    272     }
    273
    274     /**
    275      * Process the application data encryption for a session.
    276      *
    277      * @param session The session sending encrypted data to the peer.
    278      * @param message The message to encrypt
    279      * @param writeQueue The queue in which the encrypted buffer will be written
    280      * @return The written WriteRequest
    281      */
    282     /** No qualifier */
    283     WriteRequest processWrite(IoSession session, Object message, Queue<WriteRequest> writeQueue) {
    284         ByteBuffer buf = (ByteBuffer) message;
    285         ByteBuffer appBuffer = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
    286
    287         try {
    288             while (true) {
    289                 // Encrypt the message
    290                 SSLEngineResult result = sslEngine.wrap(buf, appBuffer);
    291
    292                 switch (result.getStatus()) {
    293                 case BUFFER_OVERFLOW:
    294                     // Increase the buffer size as needed
    295                     appBuffer = ByteBuffer.allocate(appBuffer.capacity() + 4096);
    296                     break;
    297
    298                 case BUFFER_UNDERFLOW:
    299                 case CLOSED:
    300                     break;
    301
    302                 case OK:
    303                     // We are done. Flip the buffer and push it to the write queue.
    304                     appBuffer.flip();
    305                     WriteRequest request = new DefaultWriteRequest(appBuffer);
    306
    307                     return request;
    308                 }
    309             }
    310         } catch (SSLException se) {
    311             throw new IllegalStateException(se.getMessage());
    312         }
    313     }
    314 }