Example #1
0
 @Override
 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
   // Make sure to release SSLEngine,
   // and notify the handshake future if the connection has been closed during handshake.
   setHandshakeFailure(ctx, CHANNEL_CLOSED);
   super.channelInactive(ctx);
 }
Example #2
0
  private void wrap(ChannelHandlerContext ctx, boolean inUnwrap) throws SSLException {
    ByteBuf out = null;
    ChannelPromise promise = null;
    ByteBufAllocator alloc = ctx.alloc();
    try {
      for (; ; ) {
        Object msg = pendingUnencryptedWrites.current();
        if (msg == null) {
          break;
        }

        ByteBuf buf = (ByteBuf) msg;
        if (out == null) {
          out = allocateOutNetBuf(ctx, buf.readableBytes());
        }

        SSLEngineResult result = wrap(alloc, engine, buf, out);
        if (!buf.isReadable()) {
          promise = pendingUnencryptedWrites.remove();
        } else {
          promise = null;
        }

        if (result.getStatus() == Status.CLOSED) {
          // SSLEngine has been closed already.
          // Any further write attempts should be denied.
          pendingUnencryptedWrites.removeAndFailAll(SSLENGINE_CLOSED);
          return;
        } else {
          switch (result.getHandshakeStatus()) {
            case NEED_TASK:
              runDelegatedTasks();
              break;
            case FINISHED:
              setHandshakeSuccess();
              // deliberate fall-through
            case NOT_HANDSHAKING:
              setHandshakeSuccessIfStillHandshaking();
              // deliberate fall-through
            case NEED_WRAP:
              finishWrap(ctx, out, promise, inUnwrap);
              promise = null;
              out = null;
              break;
            case NEED_UNWRAP:
              return;
            default:
              throw new IllegalStateException(
                  "Unknown handshake status: " + result.getHandshakeStatus());
          }
        }
      }
    } catch (SSLException e) {
      setHandshakeFailure(ctx, e);
      throw e;
    } finally {
      finishWrap(ctx, out, promise, inUnwrap);
    }
  }
Example #3
0
  private void wrapNonAppData(ChannelHandlerContext ctx, boolean inUnwrap) throws SSLException {
    ByteBuf out = null;
    try {
      for (; ; ) {
        if (out == null) {
          out = ctx.alloc().buffer(maxPacketBufferSize);
        }
        SSLEngineResult result = wrap(engine, Unpooled.EMPTY_BUFFER, out);

        if (result.bytesProduced() > 0) {
          ctx.write(out);
          if (inUnwrap) {
            needsFlush = true;
          }
          out = null;
        }

        switch (result.getHandshakeStatus()) {
          case FINISHED:
            setHandshakeSuccess();
            break;
          case NEED_TASK:
            runDelegatedTasks();
            break;
          case NEED_UNWRAP:
            if (!inUnwrap) {
              unwrapNonApp(ctx);
            }
            break;
          case NEED_WRAP:
            break;
          case NOT_HANDSHAKING:
            setHandshakeSuccessIfStillHandshaking();
            // Workaround for TLS False Start problem reported at:
            // https://github.com/netty/netty/issues/1108#issuecomment-14266970
            if (!inUnwrap) {
              unwrapNonApp(ctx);
            }
            break;
          default:
            throw new IllegalStateException(
                "Unknown handshake status: " + result.getHandshakeStatus());
        }

        if (result.bytesProduced() == 0) {
          break;
        }
      }
    } catch (SSLException e) {
      setHandshakeFailure(e);
      throw e;
    } finally {
      if (out != null) {
        out.release();
      }
    }
  }
Example #4
0
  @Override
  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    // Make sure the handshake future is notified when a connection has
    // been closed during handshake.
    setHandshakeFailure(null);

    try {
      inboundBufferUpdated(ctx);
    } finally {
      engine.closeOutbound();
      try {
        engine.closeInbound();
      } catch (SSLException ex) {
        if (logger.isDebugEnabled()) {
          logger.debug("Failed to clean up SSLEngine.", ex);
        }
      }
      ctx.fireChannelInactive();
    }
  }
Example #5
0
  /** Unwraps inbound SSL records. */
  private boolean unwrap(ChannelHandlerContext ctx, ByteBuf packet, int offset, int length)
      throws SSLException {

    boolean decoded = false;
    boolean wrapLater = false;
    boolean notifyClosure = false;
    ByteBuf decodeOut = allocate(ctx, length);
    try {
      for (; ; ) {
        final SSLEngineResult result = unwrap(engine, packet, offset, length, decodeOut);
        final Status status = result.getStatus();
        final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
        final int produced = result.bytesProduced();
        final int consumed = result.bytesConsumed();

        // Update indexes for the next iteration
        offset += consumed;
        length -= consumed;

        switch (status) {
          case BUFFER_OVERFLOW:
            int readableBytes = decodeOut.readableBytes();
            if (readableBytes > 0) {
              decoded = true;
              ctx.fireChannelRead(decodeOut);
            } else {
              decodeOut.release();
            }
            // Allocate a new buffer which can hold all the rest data and loop again.
            // TODO: We may want to reconsider how we calculate the length here as we may
            // have more then one ssl message to decode.
            decodeOut =
                allocate(ctx, engine.getSession().getApplicationBufferSize() - readableBytes);
            continue;
          case CLOSED:
            // notify about the CLOSED state of the SSLEngine. See #137
            notifyClosure = true;
            break;
          default:
            break;
        }

        switch (handshakeStatus) {
          case NEED_UNWRAP:
            break;
          case NEED_WRAP:
            wrapNonAppData(ctx, true);
            break;
          case NEED_TASK:
            runDelegatedTasks();
            break;
          case FINISHED:
            setHandshakeSuccess();
            wrapLater = true;
            continue;
          case NOT_HANDSHAKING:
            if (setHandshakeSuccessIfStillHandshaking()) {
              wrapLater = true;
              continue;
            }
            if (flushedBeforeHandshake) {
              // We need to call wrap(...) in case there was a flush done before the handshake
              // completed.
              //
              // See https://github.com/netty/netty/pull/2437
              flushedBeforeHandshake = false;
              wrapLater = true;
            }

            break;
          default:
            throw new IllegalStateException("unknown handshake status: " + handshakeStatus);
        }

        if (status == Status.BUFFER_UNDERFLOW || consumed == 0 && produced == 0) {
          break;
        }
      }

      if (wrapLater) {
        wrap(ctx, true);
      }

      if (notifyClosure) {
        sslCloseFuture.trySuccess(ctx.channel());
      }
    } catch (SSLException e) {
      setHandshakeFailure(ctx, e);
      throw e;
    } finally {
      if (decodeOut.isReadable()) {
        decoded = true;

        ctx.fireChannelRead(decodeOut);
      } else {
        decodeOut.release();
      }
    }
    return decoded;
  }
Example #6
0
  @Override
  protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
      throws SSLException {
    final int startOffset = in.readerIndex();
    final int endOffset = in.writerIndex();
    int offset = startOffset;
    int totalLength = 0;

    // If we calculated the length of the current SSL record before, use that information.
    if (packetLength > 0) {
      if (endOffset - startOffset < packetLength) {
        return;
      } else {
        offset += packetLength;
        totalLength = packetLength;
        packetLength = 0;
      }
    }

    boolean nonSslRecord = false;

    while (totalLength < OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) {
      final int readableBytes = endOffset - offset;
      if (readableBytes < 5) {
        break;
      }

      final int packetLength = getEncryptedPacketLength(in, offset);
      if (packetLength == -1) {
        nonSslRecord = true;
        break;
      }

      assert packetLength > 0;

      if (packetLength > readableBytes) {
        // wait until the whole packet can be read
        this.packetLength = packetLength;
        break;
      }

      int newTotalLength = totalLength + packetLength;
      if (newTotalLength > OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) {
        // Don't read too much.
        break;
      }

      // We have a whole packet.
      // Increment the offset to handle the next packet.
      offset += packetLength;
      totalLength = newTotalLength;
    }

    if (totalLength > 0) {
      boolean decoded = false;

      // The buffer contains one or more full SSL records.
      // Slice out the whole packet so unwrap will only be called with complete packets.
      // Also directly reset the packetLength. This is needed as unwrap(..) may trigger
      // decode(...) again via:
      // 1) unwrap(..) is called
      // 2) wrap(...) is called from within unwrap(...)
      // 3) wrap(...) calls unwrapLater(...)
      // 4) unwrapLater(...) calls decode(...)
      //
      // See https://github.com/netty/netty/issues/1534

      in.skipBytes(totalLength);

      // If SSLEngine expects a heap buffer for unwrapping, do the conversion.
      if (in.isDirect() && wantsInboundHeapBuffer) {
        ByteBuf copy = ctx.alloc().heapBuffer(totalLength);
        try {
          copy.writeBytes(in, startOffset, totalLength);
          decoded = unwrap(ctx, copy, 0, totalLength);
        } finally {
          copy.release();
        }
      } else {
        decoded = unwrap(ctx, in, startOffset, totalLength);
      }

      if (!firedChannelRead) {
        // Check first if firedChannelRead is not set yet as it may have been set in a
        // previous decode(...) call.
        firedChannelRead = decoded;
      }
    }

    if (nonSslRecord) {
      // Not an SSL/TLS packet
      NotSslRecordException e =
          new NotSslRecordException("not an SSL/TLS record: " + ByteBufUtil.hexDump(in));
      in.skipBytes(in.readableBytes());
      ctx.fireExceptionCaught(e);
      setHandshakeFailure(ctx, e);
    }
  }
Example #7
0
  private void wrapNonAppData(ChannelHandlerContext ctx, boolean inUnwrap) throws SSLException {
    ByteBuf out = null;
    ByteBufAllocator alloc = ctx.alloc();
    try {
      for (; ; ) {
        if (out == null) {
          out = allocateOutNetBuf(ctx, 0);
        }
        SSLEngineResult result = wrap(alloc, engine, Unpooled.EMPTY_BUFFER, out);

        if (result.bytesProduced() > 0) {
          ctx.write(out);
          if (inUnwrap) {
            needsFlush = true;
          }
          out = null;
        }

        switch (result.getHandshakeStatus()) {
          case FINISHED:
            setHandshakeSuccess();
            break;
          case NEED_TASK:
            runDelegatedTasks();
            break;
          case NEED_UNWRAP:
            if (!inUnwrap) {
              unwrapNonAppData(ctx);
            }
            break;
          case NEED_WRAP:
            break;
          case NOT_HANDSHAKING:
            setHandshakeSuccessIfStillHandshaking();
            // Workaround for TLS False Start problem reported at:
            // https://github.com/netty/netty/issues/1108#issuecomment-14266970
            if (!inUnwrap) {
              unwrapNonAppData(ctx);
            }
            break;
          default:
            throw new IllegalStateException(
                "Unknown handshake status: " + result.getHandshakeStatus());
        }

        if (result.bytesProduced() == 0) {
          break;
        }

        // It should not consume empty buffers when it is not handshaking
        // Fix for Android, where it was encrypting empty buffers even when not handshaking
        if (result.bytesConsumed() == 0
            && result.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
          break;
        }
      }
    } catch (SSLException e) {
      setHandshakeFailure(ctx, e);
      throw e;
    } finally {
      if (out != null) {
        out.release();
      }
    }
  }
Example #8
0
  @Override
  public void inboundBufferUpdated(final ChannelHandlerContext ctx) throws Exception {
    final ByteBuf in = ctx.inboundByteBuffer();

    if (in.readableBytes() < 5) {
      return;
    }

    int packetLength = getEncryptedPacketLength(in);

    if (packetLength == -1) {
      // Bad data - discard the buffer and raise an exception.
      NotSslRecordException e =
          new NotSslRecordException("not an SSL/TLS record: " + ByteBufUtil.hexDump(in));
      in.skipBytes(in.readableBytes());
      ctx.fireExceptionCaught(e);
      setHandshakeFailure(e);
      return;
    }

    assert packetLength > 0;

    final ByteBuf out = ctx.nextInboundByteBuffer();
    out.discardReadBytes();

    boolean wrapLater = false;
    int bytesProduced = 0;
    try {
      loop:
      for (; ; ) {
        SSLEngineResult result = unwrap(engine, in, out);
        bytesProduced += result.bytesProduced();

        switch (result.getStatus()) {
          case CLOSED:
            // notify about the CLOSED state of the SSLEngine. See #137
            sslCloseFuture.setClosed();
            break;
          case BUFFER_UNDERFLOW:
            break loop;
        }

        switch (result.getHandshakeStatus()) {
          case NEED_UNWRAP:
            break;
          case NEED_WRAP:
            wrapLater = true;
            break;
          case NEED_TASK:
            runDelegatedTasks();
            break;
          case FINISHED:
            setHandshakeSuccess();
            wrapLater = true;
            continue;
          case NOT_HANDSHAKING:
            break;
          default:
            throw new IllegalStateException(
                "Unknown handshake status: " + result.getHandshakeStatus());
        }

        if (result.bytesConsumed() == 0 && result.bytesProduced() == 0) {
          break;
        }
      }

      if (wrapLater) {
        flush(ctx, ctx.newFuture());
      }
    } catch (SSLException e) {
      setHandshakeFailure(e);
      throw e;
    } finally {
      if (bytesProduced > 0) {
        in.discardReadBytes();
        ctx.fireInboundBufferUpdated();
      }
    }
  }
Example #9
0
  @Override
  public void flush(final ChannelHandlerContext ctx, ChannelFuture future) throws Exception {
    final ByteBuf in = ctx.outboundByteBuffer();
    final ByteBuf out = ctx.nextOutboundByteBuffer();

    out.unsafe().discardSomeReadBytes();

    // Do not encrypt the first write request if this handler is
    // created with startTLS flag turned on.
    if (startTls && !sentFirstMessage) {
      sentFirstMessage = true;
      out.writeBytes(in);
      ctx.flush(future);
      return;
    }

    if (ctx.executor() == ctx.channel().eventLoop()) {
      flushFutureNotifier.addFlushFuture(future, in.readableBytes());
    } else {
      synchronized (flushFutureNotifier) {
        flushFutureNotifier.addFlushFuture(future, in.readableBytes());
      }
    }

    boolean unwrapLater = false;
    int bytesConsumed = 0;
    try {
      for (; ; ) {
        SSLEngineResult result = wrap(engine, in, out);
        bytesConsumed += result.bytesConsumed();
        if (result.getStatus() == Status.CLOSED) {
          // SSLEngine has been closed already.
          // Any further write attempts should be denied.
          if (in.readable()) {
            in.clear();
            SSLException e = new SSLException("SSLEngine already closed");
            future.setFailure(e);
            ctx.fireExceptionCaught(e);
            flush0(ctx, bytesConsumed, e);
            bytesConsumed = 0;
          }
          break;
        } else {
          switch (result.getHandshakeStatus()) {
            case NEED_WRAP:
              ctx.flush();
              continue;
            case NEED_UNWRAP:
              if (ctx.inboundByteBuffer().readable()) {
                unwrapLater = true;
              }
              break;
            case NEED_TASK:
              runDelegatedTasks();
              continue;
            case FINISHED:
              setHandshakeSuccess();
              continue;
            case NOT_HANDSHAKING:
              break;
            default:
              throw new IllegalStateException(
                  "Unknown handshake status: " + result.getHandshakeStatus());
          }

          if (result.bytesConsumed() == 0 && result.bytesProduced() == 0) {
            break;
          }
        }
      }

      if (unwrapLater) {
        inboundBufferUpdated(ctx);
      }
    } catch (SSLException e) {
      setHandshakeFailure(e);
      throw e;
    } finally {
      in.unsafe().discardSomeReadBytes();
      flush0(ctx, bytesConsumed);
    }
  }
Example #10
0
  /** Unwraps a single SSL record. */
  private void unwrapSingle(
      ChannelHandlerContext ctx, ByteBuffer packet, int initialOutAppBufCapacity)
      throws SSLException {

    boolean wrapLater = false;
    try {
      for (; ; ) {
        if (decodeOut == null) {
          decodeOut = ctx.alloc().buffer(initialOutAppBufCapacity);
        }

        final SSLEngineResult result = unwrap(engine, packet, decodeOut);
        final Status status = result.getStatus();
        final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
        final int produced = result.bytesProduced();
        final int consumed = result.bytesConsumed();

        if (status == Status.CLOSED) {
          // notify about the CLOSED state of the SSLEngine. See #137
          sslCloseFuture.trySuccess(ctx.channel());
          break;
        }

        switch (handshakeStatus) {
          case NEED_UNWRAP:
            break;
          case NEED_WRAP:
            wrapNonAppData(ctx, true);
            break;
          case NEED_TASK:
            runDelegatedTasks();
            break;
          case FINISHED:
            setHandshakeSuccess();
            wrapLater = true;
            continue;
          case NOT_HANDSHAKING:
            if (setHandshakeSuccessIfStillHandshaking()) {
              wrapLater = true;
              continue;
            }
            if (flushedBeforeHandshakeDone) {
              // We need to call wrap(...) in case there was a flush done before the handshake
              // completed.
              //
              // See https://github.com/netty/netty/pull/2437
              flushedBeforeHandshakeDone = false;
              wrapLater = true;
            }

            break;
          default:
            throw new IllegalStateException("Unknown handshake status: " + handshakeStatus);
        }

        if (status == Status.BUFFER_UNDERFLOW || consumed == 0 && produced == 0) {
          break;
        }
      }

      if (wrapLater) {
        wrap(ctx, true);
      }
    } catch (SSLException e) {
      setHandshakeFailure(e);
      throw e;
    }
  }
Example #11
0
  @Override
  protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
      throws SSLException {

    // Keeps the list of the length of every SSL record in the input buffer.
    int[] recordLengths = null;
    int nRecords = 0;

    final int startOffset = in.readerIndex();
    final int endOffset = in.writerIndex();
    int offset = startOffset;

    // If we calculated the length of the current SSL record before, use that information.
    if (packetLength > 0) {
      if (endOffset - startOffset < packetLength) {
        return;
      } else {
        recordLengths = new int[4];
        recordLengths[0] = packetLength;
        nRecords = 1;

        offset += packetLength;
        packetLength = 0;
      }
    }

    boolean nonSslRecord = false;

    for (; ; ) {
      final int readableBytes = endOffset - offset;
      if (readableBytes < 5) {
        break;
      }

      final int packetLength = getEncryptedPacketLength(in, offset);
      if (packetLength == -1) {
        nonSslRecord = true;
        break;
      }

      assert packetLength > 0;

      if (packetLength > readableBytes) {
        // wait until the whole packet can be read
        this.packetLength = packetLength;
        break;
      }

      // We have a whole packet.
      // Remember the length of the current packet.
      if (recordLengths == null) {
        recordLengths = new int[4];
      }
      if (nRecords == recordLengths.length) {
        recordLengths = Arrays.copyOf(recordLengths, recordLengths.length << 1);
      }
      recordLengths[nRecords++] = packetLength;

      // Increment the offset to handle the next packet.
      offset += packetLength;
    }

    final int totalLength = offset - startOffset;
    if (totalLength > 0) {
      // The buffer contains one or more full SSL records.
      // Slice out the whole packet so unwrap will only be called with complete packets.
      // Also directly reset the packetLength. This is needed as unwrap(..) may trigger
      // decode(...) again via:
      // 1) unwrap(..) is called
      // 2) wrap(...) is called from within unwrap(...)
      // 3) wrap(...) calls unwrapLater(...)
      // 4) unwrapLater(...) calls decode(...)
      //
      // See https://github.com/netty/netty/issues/1534
      in.skipBytes(totalLength);
      ByteBuffer buffer = in.nioBuffer(startOffset, totalLength);
      unwrapMultiple(ctx, buffer, totalLength, recordLengths, nRecords, out);
    }

    if (nonSslRecord) {
      // Not an SSL/TLS packet
      NotSslRecordException e =
          new NotSslRecordException("not an SSL/TLS record: " + ByteBufUtil.hexDump(in));
      in.skipBytes(in.readableBytes());
      ctx.fireExceptionCaught(e);
      setHandshakeFailure(e);
    }
  }
Example #12
0
  private void wrap(ChannelHandlerContext ctx, boolean inUnwrap) throws SSLException {
    ByteBuf out = null;
    ChannelPromise promise = null;
    try {
      for (; ; ) {
        PendingWrite pending = pendingUnencryptedWrites.peek();
        if (pending == null) {
          break;
        }
        if (out == null) {
          out = ctx.alloc().buffer(maxPacketBufferSize);
        }

        if (!(pending.msg() instanceof ByteBuf)) {
          ctx.write(pending.msg(), (ChannelPromise) pending.recycleAndGet());
          pendingUnencryptedWrites.remove();
          continue;
        }
        ByteBuf buf = (ByteBuf) pending.msg();
        SSLEngineResult result = wrap(engine, buf, out);

        if (!buf.isReadable()) {
          buf.release();
          promise = (ChannelPromise) pending.recycleAndGet();
          pendingUnencryptedWrites.remove();
        } else {
          promise = null;
        }

        if (result.getStatus() == Status.CLOSED) {
          // SSLEngine has been closed already.
          // Any further write attempts should be denied.
          for (; ; ) {
            PendingWrite w = pendingUnencryptedWrites.poll();
            if (w == null) {
              break;
            }
            w.failAndRecycle(SSLENGINE_CLOSED);
          }
          return;
        } else {
          switch (result.getHandshakeStatus()) {
            case NEED_TASK:
              runDelegatedTasks();
              break;
            case FINISHED:
              setHandshakeSuccess();
              // deliberate fall-through
            case NOT_HANDSHAKING:
              setHandshakeSuccessIfStillHandshaking();
              // deliberate fall-through
            case NEED_WRAP:
              finishWrap(ctx, out, promise, inUnwrap);
              promise = null;
              out = null;
              break;
            case NEED_UNWRAP:
              return;
            default:
              throw new IllegalStateException(
                  "Unknown handshake status: " + result.getHandshakeStatus());
          }
        }
      }
    } catch (SSLException e) {
      setHandshakeFailure(e);
      throw e;
    } finally {
      finishWrap(ctx, out, promise, inUnwrap);
    }
  }