mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
14.09.2012 1dfff197eadcf24823d7915e6eead2a850f679f9
opends/src/server/org/opends/server/extensions/TLSByteChannel.java
@@ -42,6 +42,7 @@
import java.util.Set;
import javax.net.ssl.*;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import org.opends.server.admin.std.server.LDAPConnectionHandlerCfg;
import org.opends.server.api.ClientConnection;
@@ -53,50 +54,375 @@
/**
 * A class that provides a TLS byte channel implementation.
 */
public class TLSByteChannel implements ByteChannel, ConnectionSecurityProvider
public final class TLSByteChannel implements ConnectionSecurityProvider
{
  private static final DebugTracer TRACER = getTracer();
  /**
   * Private implementation.
   */
  private final class ByteChannelImpl implements ByteChannel
  {
  private final ByteChannel socketChannel;
    /**
     * {@inheritDoc}
     */
    public void close() throws IOException
    {
      synchronized (readLock)
      {
        synchronized (writeLock)
        {
          final boolean isInitiator = !sslEngine.isInboundDone();
  private final SSLEngine sslEngine;
          try
          {
            if (!sslEngine.isOutboundDone())
            {
              sslEngine.closeOutbound();
              while (doWrapAndSend(EMPTY_BUFFER) > 0)
              {
                // Write out any remaining SSL close notifications.
              }
            }
          }
          catch (final ClosedChannelException e)
          {
            // Ignore this so that close is idempotent.
          }
          finally
          {
            try
            {
              sslEngine.closeInbound();
            }
            catch (final SSLException e)
            {
              // Not yet received peer's close notification. Ignore this if we
              // are the initiator.
              if (!isInitiator)
              {
                throw e;
              }
            }
            finally
            {
              channel.close();
            }
          }
        }
      }
    }
  // read copy to buffer
  private final ByteBuffer appData;
  // read encrypted
  private final ByteBuffer appNetData;
  // Write encrypted
  private final ByteBuffer netData;
  private final ByteBuffer tempData;
    /**
     * {@inheritDoc}
     */
    public boolean isOpen()
    {
      return !sslEngine.isOutboundDone() || !sslEngine.isInboundDone();
    }
  private final int sslBufferSize;
  private final int appBufSize;
  private boolean reading = false;
    /**
     * {@inheritDoc}
     */
    public int read(final ByteBuffer unwrappedData) throws IOException
    {
      synchronized (readLock)
      {
        // Repeat until there is some unwrapped data available or all available
        // data has been read from the underlying socket.
        if (!recvUnwrappedBuffer.hasRemaining())
        {
          final int read = doRecvAndUnwrap();
          if (read <= 0)
          {
            // No data read or end of stream.
            return read;
          }
        }
        // Copy available data.
        final int startPos = unwrappedData.position();
        if (recvUnwrappedBuffer.remaining() > unwrappedData.remaining())
        {
          // Unwrapped data does not fit in client buffer so copy one by at a
          // time: it's annoying that there is no easy way to do this with
          // ByteBuffers.
          while (unwrappedData.hasRemaining())
          {
            unwrappedData.put(recvUnwrappedBuffer.get());
          }
        }
        else
        {
          // Unwrapped data fits client buffer so block copy.
          unwrappedData.put(recvUnwrappedBuffer);
        }
        return unwrappedData.position() - startPos;
      }
    }
    /**
     * {@inheritDoc}
     */
    public int write(final ByteBuffer unwrappedData) throws IOException
    {
      // This method will block until the entire message is sent.
      final int bytesWritten = unwrappedData.remaining();
      // Synchronized in order to prevent interleaving and reordering.
      synchronized (writeLock)
      {
        // Repeat until the entire input data is written.
        while (unwrappedData.hasRemaining())
        {
          // Wrap and send the data.
          doWrapAndSend(unwrappedData);
          // Perform handshake if needed.
          if (isHandshaking(sslEngine.getHandshakeStatus()))
          {
            doHandshake(false /* isReading */);
          }
        }
      }
      return bytesWritten;
    }
    private void doHandshake(final boolean isReading) throws IOException
    {
      // This lock is probably unnecessary since tasks can be run in parallel,
      // but it adds no additional overhead so there's little harm in having
      // it.
      synchronized (handshakeLock)
      {
        while (true)
        {
          switch (sslEngine.getHandshakeStatus())
          {
          case NEED_TASK:
            Runnable runnable;
            while ((runnable = sslEngine.getDelegatedTask()) != null)
            {
              runnable.run();
            }
            break;
          case NEED_UNWRAP:
            // Block for writes, but be non-blocking for reads.
            if (isReading)
            {
              // Let doRecvAndUnwrap() deal with this.
              return;
            }
            // Need to do an unwrap (read) while writing.
            if (doRecvAndUnwrap() < 0)
            {
              throw new ClosedChannelException();
            }
            break;
          case NEED_WRAP:
            doWrapAndSend(EMPTY_BUFFER);
            break;
          default: // NOT_HANDSHAKING, FINISHED.
            return;
          }
        }
      }
    }
    // Attempt to read and unwrap the next SSL packet.
    private int doRecvAndUnwrap() throws IOException
    {
      // Synchronize SSL unwrap with channel reads.
      synchronized (unwrapLock)
      {
        // Repeat if there is underflow or overflow.
        boolean needRead = true;
        while (true)
        {
          // Read wrapped data if needed.
          if (needRead)
          {
            recvWrappedBuffer.compact(); // Prepare for append.
            final int read = channel.read(recvWrappedBuffer);
            recvWrappedBuffer.flip(); // Restore for read.
            if (read < 0)
            {
              // Peer abort?
              sslEngine.closeInbound();
              return -1;
            }
          }
          else
          {
            needRead = true;
          }
          // Unwrap.
          recvUnwrappedBuffer.compact(); // Prepare for append.
          final SSLEngineResult result = sslEngine.unwrap(recvWrappedBuffer,
              recvUnwrappedBuffer);
          recvUnwrappedBuffer.flip(); // Restore for read.
          switch (result.getStatus())
          {
          case BUFFER_OVERFLOW:
            // The unwrapped buffer is not big enough: resize and repeat.
            final int newAppSize = sslEngine.getSession()
                .getApplicationBufferSize();
            final ByteBuffer newRecvUnwrappedBuffer = ByteBuffer
                .allocate(recvUnwrappedBuffer.limit() + newAppSize);
            newRecvUnwrappedBuffer.put(recvUnwrappedBuffer);
            newRecvUnwrappedBuffer.flip();
            recvUnwrappedBuffer = newRecvUnwrappedBuffer;
            needRead = false;
            break; // Retry unwrap.
          case BUFFER_UNDERFLOW:
            // Not enough data was read. This either means that the inbound
            // buffer was too small, or not enough data is available.
            final int newPktSize = sslEngine.getSession().getPacketBufferSize();
            if (newPktSize > recvWrappedBuffer.capacity())
            {
              // Buffer needs resizing.
              final ByteBuffer newRecvWrappedBuffer = ByteBuffer
                  .allocate(newPktSize);
              newRecvWrappedBuffer.put(recvWrappedBuffer);
              newRecvWrappedBuffer.flip();
              recvWrappedBuffer = newRecvWrappedBuffer;
              break;
            }
            else
            {
              // Not enough data is available to read a complete SSL packet.
              return 0;
            }
          case CLOSED:
            // Peer sent SSL close notification.
            sslEngine.closeInbound();
            return -1;
          default: // OK
            if (recvUnwrappedBuffer.hasRemaining())
            {
              // Some application data was read so return it.
              return recvUnwrappedBuffer.remaining();
            }
            else if (isHandshaking(result.getHandshakeStatus()))
            {
              // No application data was read, but if we are handshaking then
              // try to continue.
              if (result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP
                  && !recvWrappedBuffer.hasRemaining())
              {
                // Not enough data is available to continue handshake.
                return 0;
              }
              else
              {
                // Continue handshake.
                doHandshake(true /* isReading */);
              }
            }
            else
            {
              // No data available and not handshaking.
              return 0;
            }
          }
        }
      }
    }
    // Attempt to wrap and send the next SSL packet.
    private int doWrapAndSend(final ByteBuffer unwrappedData)
        throws IOException
    {
      // Synchronize SSL wrap with channel writes.
      synchronized (wrapLock)
      {
        // Repeat while there is overflow.
        while (true)
        {
          final SSLEngineResult result = sslEngine.wrap(unwrappedData,
              sendWrappedBuffer);
          switch (result.getStatus())
          {
          case BUFFER_OVERFLOW:
            // The wrapped buffer is not big enough: resize and repeat.
            final int newSize = sslEngine.getSession().getPacketBufferSize();
            final ByteBuffer newSendWrappedBuffer = ByteBuffer
                .allocate(sendWrappedBuffer.position() + newSize);
            sendWrappedBuffer.flip();
            newSendWrappedBuffer.put(sendWrappedBuffer);
            sendWrappedBuffer = newSendWrappedBuffer;
            break; // Retry.
          case BUFFER_UNDERFLOW:
            // This should not happen for sends.
            throw new SSLException("Got unexpected underflow while wrapping");
          case CLOSED:
            throw new ClosedChannelException();
          default: // OK
            // Write the SSL packet: our IO stack will block until all the
            // data is written.
            sendWrappedBuffer.flip();
            while (sendWrappedBuffer.hasRemaining())
            {
              channel.write(sendWrappedBuffer);
            }
            final int written = sendWrappedBuffer.position();
            sendWrappedBuffer.clear();
            return written;
          }
        }
      }
    }
    private boolean isHandshaking(final HandshakeStatus status)
    {
      return status != HandshakeStatus.NOT_HANDSHAKING;
    }
  }
  // Map of cipher phrases to effective key size (bits). Taken from the
  // following RFCs: 5289, 4346, 3268,4132 and 4162.
  private static final Map<String, Integer> cipherMap;
  private static final Map<String, Integer> CIPHER_MAP;
  static
  {
    cipherMap = new LinkedHashMap<String, Integer>();
    cipherMap.put("_WITH_AES_256_CBC_", new Integer(256));
    cipherMap.put("_WITH_CAMELLIA_256_CBC_", new Integer(256));
    cipherMap.put("_WITH_AES_256_GCM_", new Integer(256));
    cipherMap.put("_WITH_3DES_EDE_CBC_", new Integer(112));
    cipherMap.put("_WITH_AES_128_GCM_", new Integer(128));
    cipherMap.put("_WITH_SEED_CBC_", new Integer(128));
    cipherMap.put("_WITH_CAMELLIA_128_CBC_", new Integer(128));
    cipherMap.put("_WITH_AES_128_CBC_", new Integer(128));
    cipherMap.put("_WITH_IDEA_CBC_", new Integer(128));
    cipherMap.put("_WITH_DES_CBC_", new Integer(56));
    cipherMap.put("_WITH_RC2_CBC_40_", new Integer(40));
    cipherMap.put("_WITH_RC4_40_", new Integer(40));
    cipherMap.put("_WITH_DES40_CBC_", new Integer(40));
    cipherMap.put("_WITH_NULL_", new Integer(0));
  };
    CIPHER_MAP = new LinkedHashMap<String, Integer>();
    CIPHER_MAP.put("_WITH_AES_256_CBC_", new Integer(256));
    CIPHER_MAP.put("_WITH_CAMELLIA_256_CBC_", new Integer(256));
    CIPHER_MAP.put("_WITH_AES_256_GCM_", new Integer(256));
    CIPHER_MAP.put("_WITH_3DES_EDE_CBC_", new Integer(112));
    CIPHER_MAP.put("_WITH_AES_128_GCM_", new Integer(128));
    CIPHER_MAP.put("_WITH_SEED_CBC_", new Integer(128));
    CIPHER_MAP.put("_WITH_CAMELLIA_128_CBC_", new Integer(128));
    CIPHER_MAP.put("_WITH_AES_128_CBC_", new Integer(128));
    CIPHER_MAP.put("_WITH_IDEA_CBC_", new Integer(128));
    CIPHER_MAP.put("_WITH_DES_CBC_", new Integer(56));
    CIPHER_MAP.put("_WITH_RC2_CBC_40_", new Integer(40));
    CIPHER_MAP.put("_WITH_RC4_40_", new Integer(40));
    CIPHER_MAP.put("_WITH_DES40_CBC_", new Integer(40));
    CIPHER_MAP.put("_WITH_NULL_", new Integer(0));
  }
  private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
  private static final DebugTracer TRACER = getTracer();
@@ -124,12 +450,28 @@
  private final ByteChannelImpl pimpl = new ByteChannelImpl();
  private final ByteChannel channel;
  private final SSLEngine sslEngine;
  private ByteBuffer recvWrappedBuffer;
  private ByteBuffer recvUnwrappedBuffer;
  private ByteBuffer sendWrappedBuffer;
  private final Object handshakeLock = new Object();
  private final Object unwrapLock = new Object();
  private final Object wrapLock = new Object();
  private final Object readLock = new Object();
  private final Object writeLock = new Object();
  private TLSByteChannel(final LDAPConnectionHandlerCfg config,
      final ClientConnection c, final ByteChannel socketChannel,
      final ClientConnection c, final ByteChannel channel,
      final SSLContext sslContext)
  {
    this.socketChannel = socketChannel;
    this.channel = channel;
    // getHostName could potentially be very expensive and could block
    // the connection handler for several minutes. (See issue 4229)
@@ -137,11 +479,14 @@
    // avoid blocking new connections. Just remove for now to prevent
    // potential DoS attacks. SSL sessions will not be reused and some
    // cipher suites (such as Kerberos) will not work.
    // String hostName = socketChannel.socket().getInetAddress().getHostName();
    // int port = socketChannel.socket().getPort();
    // sslEngine = sslContext.createSSLEngine(hostName, port);
    sslEngine = sslContext.createSSLEngine();
    sslEngine.setUseClientMode(false);
    final Set<String> protocols = config.getSSLProtocol();
    if (!protocols.isEmpty())
    {
@@ -171,15 +516,18 @@
      break;
    }
    final SSLSession sslSession = sslEngine.getSession();
    sslBufferSize = sslSession.getPacketBufferSize();
    appBufSize = sslSession.getApplicationBufferSize();
    // Allocate read/write buffers.
    final SSLSession session = sslEngine.getSession();
    final int wrappedBufferSize = session.getPacketBufferSize();
    final int unwrappedBufferSize = session.getApplicationBufferSize();
    appNetData = ByteBuffer.allocate(sslBufferSize);
    netData = ByteBuffer.allocate(sslBufferSize);
    sendWrappedBuffer = ByteBuffer.allocate(wrappedBufferSize);
    recvWrappedBuffer = ByteBuffer.allocate(wrappedBufferSize);
    recvUnwrappedBuffer = ByteBuffer.allocate(unwrappedBufferSize);
    appData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
    tempData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
    // Initially nothing has been received.
    recvWrappedBuffer.flip();
    recvUnwrappedBuffer.flip();
  }
@@ -187,27 +535,9 @@
  /**
   * {@inheritDoc}
   */
  public synchronized void close() throws IOException
  public ByteChannel getChannel()
  {
    sslEngine.closeInbound();
    sslEngine.closeOutbound();
    final SSLEngineResult.HandshakeStatus hsStatus = sslEngine
        .getHandshakeStatus();
    if (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED
        && hsStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
    {
      doHandshakeWrite(hsStatus);
    }
  }
  /**
   * {@inheritDoc}
   */
  public int getAppBufSize()
  {
    return appBufSize;
    return pimpl;
  }
@@ -248,31 +578,15 @@
   */
  public int getSSF()
  {
    int cipherKeySSF = 0;
    final String cipherString = sslEngine.getSession().getCipherSuite();
    for (final Map.Entry<String, Integer> mapEntry : cipherMap.entrySet())
    for (final Map.Entry<String, Integer> mapEntry : CIPHER_MAP.entrySet())
    {
      if (cipherString.indexOf(mapEntry.getKey()) >= 0)
      {
        cipherKeySSF = mapEntry.getValue().intValue();
        break;
        return mapEntry.getValue().intValue();
      }
    }
    return cipherKeySSF;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isOpen()
  {
    if (sslEngine.isInboundDone() || sslEngine.isOutboundDone())
    {
      return false;
    }
    return true;
    return 0;
  }
@@ -285,269 +599,4 @@
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized int read(final ByteBuffer clearBuffer) throws IOException
  {
    SSLEngineResult.HandshakeStatus hsStatus;
    if (!reading)
    {
      appNetData.clear();
    }
    else
    {
      reading = false;
    }
    if (!socketChannel.isOpen())
    {
      return -1;
    }
    if (sslEngine.isInboundDone())
    {
      return -1;
    }
    do
    {
      final int wrappedBytes = socketChannel.read(appNetData);
      appNetData.flip();
      if (wrappedBytes == -1)
      {
        return -1;
      }
      hsStatus = sslEngine.getHandshakeStatus();
      if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
          || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP)
      {
        doHandshakeRead(hsStatus);
      }
      if (wrappedBytes == 0)
      {
        return 0;
      }
      while (appNetData.hasRemaining())
      {
        appData.clear();
        final SSLEngineResult res = sslEngine.unwrap(appNetData, appData);
        appData.flip();
        if (res.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW)
        {
          appNetData.compact();
          reading = true;
          break;
        }
        else if (res.getStatus() != SSLEngineResult.Status.OK)
        {
          return -1;
        }
        hsStatus = sslEngine.getHandshakeStatus();
        if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
            || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP)
        {
          doHandshakeOp(hsStatus);
        }
        clearBuffer.put(appData);
      }
      hsStatus = sslEngine.getHandshakeStatus();
    }
    while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP);
    return clearBuffer.position();
  }
  /**
   * {@inheritDoc}
   */
  public ByteChannel wrapChannel(final ByteChannel channel)
  {
    return this;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized int write(final ByteBuffer clearData) throws IOException
  {
    if (!socketChannel.isOpen() || sslEngine.isOutboundDone())
    {
      throw new ClosedChannelException();
    }
    final int originalPosition = clearData.position();
    final int originalLimit = clearData.limit();
    final int length = originalLimit - originalPosition;
    if (length > sslBufferSize)
    {
      int pos = originalPosition;
      int lim = originalPosition + sslBufferSize;
      while (pos < originalLimit)
      {
        clearData.position(pos);
        clearData.limit(lim);
        writeInternal(clearData);
        pos = lim;
        lim = Math.min(originalLimit, pos + sslBufferSize);
      }
      return length;
    }
    else
    {
      return writeInternal(clearData);
    }
  }
  private void doHandshakeOp(SSLEngineResult.HandshakeStatus hsStatus)
      throws IOException
  {
    SSLEngineResult res;
    switch (hsStatus)
    {
    case NEED_TASK:
      hsStatus = doTasks();
      break;
    case NEED_WRAP:
      tempData.clear();
      netData.clear();
      res = sslEngine.wrap(tempData, netData);
      hsStatus = res.getHandshakeStatus();
      netData.flip();
      while (netData.hasRemaining())
      {
        socketChannel.write(netData);
      }
      hsStatus = sslEngine.getHandshakeStatus();
      return;
    default:
      return;
    }
  }
  private void doHandshakeRead(SSLEngineResult.HandshakeStatus hsStatus)
      throws IOException
  {
    do
    {
      doHandshakeOp(hsStatus);
      hsStatus = sslEngine.getHandshakeStatus();
    }
    while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK);
  }
  private void doHandshakeUnwrap() throws IOException
  {
    netData.clear();
    tempData.clear();
    final int bytesRead = socketChannel.read(netData);
    if (bytesRead <= 0)
    {
      throw new ClosedChannelException();
    }
    else
    {
      sslEngine.unwrap(netData, tempData);
    }
  }
  private void doHandshakeWrite(SSLEngineResult.HandshakeStatus hsStatus)
      throws IOException
  {
    do
    {
      if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
      {
        doHandshakeUnwrap();
      }
      else
      {
        doHandshakeOp(hsStatus);
      }
      hsStatus = sslEngine.getHandshakeStatus();
    }
    while (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP);
  }
  private SSLEngineResult.HandshakeStatus doTasks()
  {
    Runnable task;
    while ((task = sslEngine.getDelegatedTask()) != null)
    {
      task.run();
    }
    return sslEngine.getHandshakeStatus();
  }
  private int writeInternal(final ByteBuffer clearData) throws IOException
  {
    int totBytesSent = 0;
    SSLEngineResult.HandshakeStatus hsStatus;
    hsStatus = sslEngine.getHandshakeStatus();
    if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
        || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
    {
      doHandshakeWrite(hsStatus);
    }
    while (clearData.hasRemaining())
    {
      netData.clear();
      final SSLEngineResult res = sslEngine.wrap(clearData, netData);
      netData.flip();
      if (netData.remaining() == 0)
      {
        // wrap didn't produce any data from our clear buffer.
        // Throw exception to prevent looping.
        throw new SSLException("SSLEngine.wrap produced 0 bytes");
      }
      if (res.getStatus() != SSLEngineResult.Status.OK)
      {
        throw new ClosedChannelException();
      }
      if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK
          || hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP
          || hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
      {
        doHandshakeWrite(hsStatus);
      }
      totBytesSent += socketChannel.write(netData);
    }
    return totBytesSent;
  }
}