示例#1
0
  /**
   * Starts an SSL / TLS handshake for the specified channel.
   *
   * @return a {@link ChannelFuture} which is notified when the handshake succeeds or fails.
   */
  public ChannelFuture handshake() {
    if (handshaken && !isEnableRenegotiation()) {
      throw new IllegalStateException("renegotiation disabled");
    }

    ChannelHandlerContext ctx = this.ctx;
    Channel channel = ctx.getChannel();
    ChannelFuture handshakeFuture;
    synchronized (handshakeLock) {
      if (handshaking) {
        return this.handshakeFuture;
      } else {
        handshaking = true;
        try {
          engine.beginHandshake();
          runDelegatedTasks();
          handshakeFuture = this.handshakeFuture = future(channel);
        } catch (SSLException e) {
          handshakeFuture = this.handshakeFuture = failedFuture(channel, e);
        }
      }
    }

    try {
      wrapNonAppData(ctx, channel);
    } catch (SSLException e) {
      handshakeFuture.setFailure(e);
    }
    return handshakeFuture;
  }
示例#2
0
  @Test
  public void testGentleCloseDuringHandshake() throws Exception {
    InetSocketAddress address = startServer(version, null);
    SslContextFactory sslContextFactory = newSslContextFactory();
    sslContextFactory.start();
    SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
    sslEngine.setUseClientMode(true);
    NextProtoNego.put(
        sslEngine,
        new NextProtoNego.ClientProvider() {
          @Override
          public boolean supports() {
            return true;
          }

          @Override
          public void unsupported() {}

          @Override
          public String selectProtocol(List<String> protocols) {
            return null;
          }
        });
    sslEngine.beginHandshake();

    ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
    sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
    encrypted.flip();

    try (SocketChannel channel = SocketChannel.open(address)) {
      // Send ClientHello, immediately followed by TLS Close Alert and then by FIN
      channel.write(encrypted);
      sslEngine.closeOutbound();
      encrypted.clear();
      sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
      encrypted.flip();
      channel.write(encrypted);
      channel.shutdownOutput();

      // Read ServerHello from server
      encrypted.clear();
      int read = channel.read(encrypted);
      encrypted.flip();
      Assert.assertTrue(read > 0);
      // Cannot decrypt, as the SSLEngine has been already closed

      // Now if we read more, we should either read the TLS Close Alert, or directly -1
      encrypted.clear();
      read = channel.read(encrypted);
      // Sending a TLS Close Alert during handshake results in an exception when
      // unwrapping that the server react to by closing the connection abruptly.
      Assert.assertTrue(read < 0);
    }
  }
示例#3
0
 public SSLHandler(SSLEngine engine, SocketChannel channle, Selector selector)
     throws SSLException {
   this.engine = engine;
   this.channel = channle;
   this.selector = selector;
   this.appSendBuffer = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
   this.netSendBuffer = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
   this.appRecvBuffer = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
   this.netRecvBuffer = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
   engine.beginHandshake();
 }
 @Override
 public void onOpen() {
   try {
     // Begin the handshake
     _sslEngine.beginHandshake();
     super.onOpen();
     getDecryptedEndPoint().getConnection().onOpen();
   } catch (SSLException x) {
     getEndPoint().close();
     throw new RuntimeIOException(x);
   }
 }
示例#5
0
  @Test
  public void testAbruptCloseDuringHandshake() throws Exception {
    InetSocketAddress address = startServer(version, null);
    SslContextFactory sslContextFactory = newSslContextFactory();
    sslContextFactory.start();
    SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
    sslEngine.setUseClientMode(true);
    NextProtoNego.put(
        sslEngine,
        new NextProtoNego.ClientProvider() {
          @Override
          public boolean supports() {
            return true;
          }

          @Override
          public void unsupported() {}

          @Override
          public String selectProtocol(List<String> protocols) {
            return null;
          }
        });
    sslEngine.beginHandshake();

    ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
    sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
    encrypted.flip();

    try (SocketChannel channel = SocketChannel.open(address)) {
      // Send ClientHello, immediately followed by FIN (no TLS Close Alert)
      channel.write(encrypted);
      channel.shutdownOutput();

      // Read ServerHello from server
      encrypted.clear();
      int read = channel.read(encrypted);
      encrypted.flip();
      Assert.assertTrue(read > 0);
      ByteBuffer decrypted = ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize());
      sslEngine.unwrap(encrypted, decrypted);

      // Now if we read more, we should either read the TLS Close Alert, or directly -1
      encrypted.clear();
      read = channel.read(encrypted);
      // Since we have close the connection abruptly, the server also does so
      Assert.assertTrue(read < 0);
    }
  }
示例#6
0
    private synchronized SSLEngine newSslEngine(SocketChannel channel) throws IOException {
      SslContextFactory sslContextFactory = _httpClient.getSslContextFactory();
      SSLEngine sslEngine;
      if (channel != null) {
        String peerHost = channel.socket().getInetAddress().getHostAddress();
        int peerPort = channel.socket().getPort();
        sslEngine = sslContextFactory.newSslEngine(peerHost, peerPort);
      } else {
        sslEngine = sslContextFactory.newSslEngine();
      }
      sslEngine.setUseClientMode(true);
      sslEngine.beginHandshake();

      return sslEngine;
    }
示例#7
0
  private Future<Channel> handshake() {
    final ScheduledFuture<?> timeoutFuture;
    if (handshakeTimeoutMillis > 0) {
      timeoutFuture =
          ctx.executor()
              .schedule(
                  new Runnable() {
                    @Override
                    public void run() {
                      if (handshakePromise.isDone()) {
                        return;
                      }
                      notifyHandshakeFailure(HANDSHAKE_TIMED_OUT);
                    }
                  },
                  handshakeTimeoutMillis,
                  TimeUnit.MILLISECONDS);
    } else {
      timeoutFuture = null;
    }

    handshakePromise.addListener(
        new GenericFutureListener<Future<Channel>>() {
          @Override
          public void operationComplete(Future<Channel> f) throws Exception {
            if (timeoutFuture != null) {
              timeoutFuture.cancel(false);
            }
          }
        });
    try {
      engine.beginHandshake();
      wrapNonAppData(ctx, false);
      ctx.flush();
    } catch (Exception e) {
      notifyHandshakeFailure(e);
    }
    return handshakePromise;
  }
示例#8
0
  /**
   * Execute a handshake with the client socket channel
   *
   * @throws Exception
   */
  private void doHandshake() throws Exception {

    SSLSession session = getSSLSession();
    int packetBufferSize = Math.max(session.getPacketBufferSize(), MIN_BUFFER_SIZE);
    // Create byte buffers to use for holding application data
    initBuffers(packetBufferSize);

    ByteBuffer clientNetData = ByteBuffer.allocateDirect(packetBufferSize);
    ByteBuffer clientAppData = ByteBuffer.allocateDirect(packetBufferSize);

    // Begin handshake
    sslEngine.beginHandshake();
    handshakeStatus = sslEngine.getHandshakeStatus();
    int i = 1;
    boolean read = true;
    // Process handshaking message
    while (!handshakeComplete) {

      switch (handshakeStatus) {
        case NEED_UNWRAP:
          int nBytes = 0;
          if (read) {
            clientAppData.clear();
            nBytes = this.channel.read(this.netInBuffer).get();
          }
          if (nBytes < 0) {
            throw new IOException(MESSAGES.errorUnwrappingHandshake());
          } else {
            boolean cont = false;
            // Loop while we can perform pure SSLEngine data
            do {
              // Prepare the buffer with the incoming data
              this.netInBuffer.flip();
              // Call unwrap
              SSLEngineResult res = sslEngine.unwrap(this.netInBuffer, clientAppData);
              // Compact the buffer, this is an optional method,
              // wonder what would happen if we didn't
              this.netInBuffer.compact();
              // Read in the status
              handshakeStatus = res.getHandshakeStatus();
              if (res.getStatus() == SSLEngineResult.Status.OK) {
                // Execute tasks if we need to
                tryTasks();
                read = true;
              } else if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
                read = true;
              } else if (res.getStatus() == Status.BUFFER_OVERFLOW) {
                ByteBuffer tmp = ByteBuffer.allocateDirect(packetBufferSize * (++i));

                if (clientAppData.position() > 0) {
                  clientAppData.flip();
                }
                tmp.put(clientAppData);
                clientAppData = tmp;
                read = false;
              }
              // Perform another unwrap?
              cont =
                  res.getStatus() == SSLEngineResult.Status.OK
                      && handshakeStatus == HandshakeStatus.NEED_UNWRAP;
            } while (cont);
          }

          break;
        case NEED_WRAP:
          clientNetData.compact();
          this.netOutBuffer.clear();
          SSLEngineResult res = sslEngine.wrap(clientNetData, this.netOutBuffer);
          handshakeStatus = res.getHandshakeStatus();
          this.netOutBuffer.flip();

          if (res.getStatus() == Status.OK) {
            // Execute tasks if we need to
            tryTasks();
            // Send the handshaking data to client
            while (this.netOutBuffer.hasRemaining()) {
              if (this.channel.write(this.netOutBuffer).get() < 0) {
                // Handle closed channel
                throw new IOException(MESSAGES.errorWrappingHandshake());
              }
            }
          } else {
            // Wrap should always work with our buffers
            throw new IOException(
                MESSAGES.errorWrappingHandshakeStatus(res.getStatus().toString()));
          }

          break;
        case NEED_TASK:
          handshakeStatus = tasks();

          break;
        case NOT_HANDSHAKING:
          throw new SSLHandshakeException(MESSAGES.notHandshaking());
        case FINISHED:
          handshakeComplete = true;
          break;
      }
    }

    this.handshakeComplete = (handshakeStatus == HandshakeStatus.FINISHED);
  }
 private void handshake() throws IOException {
   if (handshakeCompleted) {
     return;
   }
   if (DEBUG) {
     log("Starting handshake...");
   }
   synchronized (this) {
     if (handshakeCompleted) {
       if (DEBUG) {
         log("Handshake already completed...");
       }
       return;
     }
     int counter = 0;
     if (DEBUG) {
       log("Begin handshake");
     }
     sslEngine.beginHandshake();
     writeInternal(emptyBuffer);
     while (counter++ < 250
         && sslEngineResult.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.FINISHED) {
       if (DEBUG) {
         log("Handshake status: " + sslEngineResult.getHandshakeStatus());
       }
       if (sslEngineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
         if (DEBUG) {
           log("Begin UNWRAP");
         }
         netInBuffer.clear();
         while (socketChannel.read(netInBuffer) < 1) {
           try {
             if (DEBUG) {
               log("Spinning on channel read...");
             }
             Thread.sleep(50);
           } catch (InterruptedException e) {
             throw new IOException(e);
           }
         }
         netInBuffer.flip();
         unwrap(netInBuffer);
         if (DEBUG) {
           log("Done UNWRAP: " + sslEngineResult.getHandshakeStatus());
         }
         if (sslEngineResult.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.FINISHED) {
           emptyBuffer.clear();
           writeInternal(emptyBuffer);
           if (DEBUG) {
             log("Done WRAP after UNWRAP: " + sslEngineResult.getHandshakeStatus());
           }
         }
       } else if (sslEngineResult.getHandshakeStatus()
           == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
         if (DEBUG) {
           log("Begin WRAP");
         }
         emptyBuffer.clear();
         writeInternal(emptyBuffer);
         if (DEBUG) {
           log("Done WRAP: " + sslEngineResult.getHandshakeStatus());
         }
       } else {
         try {
           if (DEBUG) {
             log("Sleeping... Status: " + sslEngineResult.getHandshakeStatus());
           }
           Thread.sleep(500);
         } catch (InterruptedException e) {
           throw new IOException(e);
         }
       }
     }
     if (sslEngineResult.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.FINISHED) {
       throw new SSLHandshakeException(
           "SSL handshake failed after "
               + counter
               + " trials! -> "
               + sslEngineResult.getHandshakeStatus());
     }
     if (DEBUG) {
       log("Handshake completed!");
     }
     in.clear();
     in.flip();
     handshakeCompleted = true;
   }
 }
示例#10
0
  /**
   * Performs TLS (re)negotiation.
   *
   * @param newHandshakePromise if {@code null}, use the existing {@link #handshakePromise},
   *     assuming that the current negotiation has not been finished. Currently, {@code null} is
   *     expected only for the initial handshake.
   */
  private void handshake(final Promise<Channel> newHandshakePromise) {
    final Promise<Channel> p;
    if (newHandshakePromise != null) {
      final Promise<Channel> oldHandshakePromise = handshakePromise;
      if (!oldHandshakePromise.isDone()) {
        // There's no need to handshake because handshake is in progress already.
        // Merge the new promise into the old one.
        oldHandshakePromise.addListener(
            new FutureListener<Channel>() {
              @Override
              public void operationComplete(Future<Channel> future) throws Exception {
                if (future.isSuccess()) {
                  newHandshakePromise.setSuccess(future.getNow());
                } else {
                  newHandshakePromise.setFailure(future.cause());
                }
              }
            });
        return;
      }

      handshakePromise = p = newHandshakePromise;
    } else {
      // Forced to reuse the old handshake.
      p = handshakePromise;
      assert !p.isDone();
    }

    // Begin handshake.
    final ChannelHandlerContext ctx = this.ctx;
    try {
      engine.beginHandshake();
      wrapNonAppData(ctx, false);
      ctx.flush();
    } catch (Exception e) {
      notifyHandshakeFailure(e);
    }

    // Set timeout if necessary.
    final long handshakeTimeoutMillis = this.handshakeTimeoutMillis;
    if (handshakeTimeoutMillis <= 0 || p.isDone()) {
      return;
    }

    final ScheduledFuture<?> timeoutFuture =
        ctx.executor()
            .schedule(
                new Runnable() {
                  @Override
                  public void run() {
                    if (p.isDone()) {
                      return;
                    }
                    notifyHandshakeFailure(HANDSHAKE_TIMED_OUT);
                  }
                },
                handshakeTimeoutMillis,
                TimeUnit.MILLISECONDS);

    // Cancel the handshake timeout when handshake is finished.
    p.addListener(
        new FutureListener<Channel>() {
          @Override
          public void operationComplete(Future<Channel> f) throws Exception {
            timeoutFuture.cancel(false);
          }
        });
  }
示例#11
0
  /**
   * Does the handshake of the two specified engines according to the {@code mode} specified.
   *
   * @param clientEngine - Client SSLEngine.
   * @param serverEngine - Server SSLEngine.
   * @param maxPacketSize - Maximum packet size for MFLN of zero for no limit.
   * @param mode - Handshake mode according to {@link HandshakeMode} enum.
   * @param enableReplicatedPacks - Set {@code true} to enable replicated packet sending.
   * @throws SSLException - thrown on engine errors.
   */
  public static void doHandshake(
      SSLEngine clientEngine,
      SSLEngine serverEngine,
      int maxPacketSize,
      HandshakeMode mode,
      boolean enableReplicatedPacks)
      throws SSLException {

    System.out.println("=============================================");
    System.out.println("Starting handshake " + mode.name());
    int loop = 0;
    if (maxPacketSize < 0) {
      throw new Error("Test issue: maxPacketSize is less than zero!");
    }
    SSLParameters params = clientEngine.getSSLParameters();
    params.setMaximumPacketSize(maxPacketSize);
    clientEngine.setSSLParameters(params);
    params = serverEngine.getSSLParameters();
    params.setMaximumPacketSize(maxPacketSize);
    serverEngine.setSSLParameters(params);
    SSLEngine firstEngine;
    SSLEngine secondEngine;
    switch (mode) {
      case INITIAL_HANDSHAKE:
        firstEngine = clientEngine;
        secondEngine = serverEngine;
        doUnwrapForNotHandshakingStatus = false;
        clientEngine.beginHandshake();
        serverEngine.beginHandshake();
        break;
      case REHANDSHAKE_BEGIN_CLIENT:
        firstEngine = clientEngine;
        secondEngine = serverEngine;
        doUnwrapForNotHandshakingStatus = true;
        clientEngine.beginHandshake();
        break;
      case REHANDSHAKE_BEGIN_SERVER:
        firstEngine = serverEngine;
        secondEngine = clientEngine;
        doUnwrapForNotHandshakingStatus = true;
        serverEngine.beginHandshake();
        break;
      default:
        throw new Error("Test issue: unknown handshake mode");
    }
    endHandshakeLoop = false;
    while (!endHandshakeLoop) {
      if (++loop > MAX_HANDSHAKE_LOOPS) {
        throw new Error("Too much loops for handshaking");
      }
      System.out.println("============================================");
      System.out.println("Handshake loop " + loop + ": round 1");
      System.out.println("==========================");
      handshakeProcess(firstEngine, secondEngine, maxPacketSize, enableReplicatedPacks);
      if (endHandshakeLoop) {
        break;
      }
      System.out.println("Handshake loop " + loop + ": round 2");
      System.out.println("==========================");
      handshakeProcess(secondEngine, firstEngine, maxPacketSize, enableReplicatedPacks);
    }
  }
示例#12
0
 void beginHandshake() throws SSLException {
   _engine.beginHandshake();
 }
  @Test
  public void testTcpClose() throws Exception {
    // This test replaces SSLSocket() with a very manual SSL client
    // so we can close TCP underneath SSL.

    SocketChannel client = SocketChannel.open(_connector.socket().getLocalSocketAddress());
    client.socket().setSoTimeout(500);

    SocketChannel server = _connector.accept();
    server.configureBlocking(false);
    _manager.accept(server);

    SSLEngine engine = __sslCtxFactory.newSSLEngine();
    engine.setUseClientMode(true);
    engine.beginHandshake();

    ByteBuffer appOut = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
    ByteBuffer sslOut = ByteBuffer.allocate(engine.getSession().getPacketBufferSize() * 2);
    ByteBuffer appIn = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
    ByteBuffer sslIn = ByteBuffer.allocate(engine.getSession().getPacketBufferSize() * 2);

    boolean debug = false;

    if (debug) System.err.println(engine.getHandshakeStatus());
    int loop = 20;
    while (engine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) {
      if (--loop == 0) throw new IllegalStateException();

      if (engine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
        if (debug)
          System.err.printf(
              "sslOut %d-%d-%d%n", sslOut.position(), sslOut.limit(), sslOut.capacity());
        if (debug)
          System.err.printf(
              "appOut %d-%d-%d%n", appOut.position(), appOut.limit(), appOut.capacity());
        SSLEngineResult result = engine.wrap(appOut, sslOut);
        if (debug) System.err.println(result);
        sslOut.flip();
        int flushed = client.write(sslOut);
        if (debug) System.err.println("out=" + flushed);
        sslOut.clear();
      }

      if (engine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
        if (debug)
          System.err.printf("sslIn %d-%d-%d%n", sslIn.position(), sslIn.limit(), sslIn.capacity());
        if (sslIn.position() == 0) {
          int filled = client.read(sslIn);
          if (debug) System.err.println("in=" + filled);
        }
        sslIn.flip();
        if (debug)
          System.err.printf("sslIn %d-%d-%d%n", sslIn.position(), sslIn.limit(), sslIn.capacity());
        SSLEngineResult result = engine.unwrap(sslIn, appIn);
        if (debug) System.err.println(result);
        if (debug)
          System.err.printf("sslIn %d-%d-%d%n", sslIn.position(), sslIn.limit(), sslIn.capacity());
        if (sslIn.hasRemaining()) sslIn.compact();
        else sslIn.clear();
        if (debug)
          System.err.printf("sslIn %d-%d-%d%n", sslIn.position(), sslIn.limit(), sslIn.capacity());
      }

      if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
        Runnable task;
        while ((task = engine.getDelegatedTask()) != null) task.run();
        if (debug) System.err.println(engine.getHandshakeStatus());
      }
    }

    if (debug) System.err.println("\nSay Hello");

    // write a message
    appOut.put("HelloWorld".getBytes(StandardCharsets.UTF_8));
    appOut.flip();
    SSLEngineResult result = engine.wrap(appOut, sslOut);
    if (debug) System.err.println(result);
    sslOut.flip();
    int flushed = client.write(sslOut);
    if (debug) System.err.println("out=" + flushed);
    sslOut.clear();
    appOut.clear();

    // read the response
    int filled = client.read(sslIn);
    if (debug) System.err.println("in=" + filled);
    sslIn.flip();
    result = engine.unwrap(sslIn, appIn);
    if (debug) System.err.println(result);
    if (sslIn.hasRemaining()) sslIn.compact();
    else sslIn.clear();

    appIn.flip();
    String reply = new String(appIn.array(), appIn.arrayOffset(), appIn.remaining());
    appIn.clear();

    Assert.assertEquals("HelloWorld", reply);

    if (debug) System.err.println("Shutting down output");
    client.socket().shutdownOutput();

    filled = client.read(sslIn);
    if (debug) System.err.println("in=" + filled);

    if (filled >= 0) {
      // this is the old behaviour.
      sslIn.flip();
      try {
        // Since the client closed abruptly, the server is sending a close alert with a failure
        engine.unwrap(sslIn, appIn);
        Assert.fail();
      } catch (SSLException x) {
        // Expected
      }
    }

    sslIn.clear();
    filled = client.read(sslIn);
    Assert.assertEquals(-1, filled);

    Assert.assertFalse(server.isOpen());
  }
  @Test
  public void checkSslEngineBehaviour() throws Exception {
    SSLEngine server = __sslCtxFactory.newSSLEngine();
    SSLEngine client = __sslCtxFactory.newSSLEngine();

    ByteBuffer netC2S = ByteBuffer.allocate(server.getSession().getPacketBufferSize());
    ByteBuffer netS2C = ByteBuffer.allocate(server.getSession().getPacketBufferSize());
    ByteBuffer serverIn = ByteBuffer.allocate(server.getSession().getApplicationBufferSize());
    ByteBuffer serverOut = ByteBuffer.allocate(server.getSession().getApplicationBufferSize());
    ByteBuffer clientIn = ByteBuffer.allocate(client.getSession().getApplicationBufferSize());

    SSLEngineResult result;

    // start the client
    client.setUseClientMode(true);
    client.beginHandshake();
    Assert.assertEquals(HandshakeStatus.NEED_WRAP, client.getHandshakeStatus());

    // what if we try an unwrap?
    netS2C.flip();
    result = client.unwrap(netS2C, clientIn);
    // unwrap is a noop
    assertEquals(SSLEngineResult.Status.OK, result.getStatus());
    assertEquals(0, result.bytesConsumed());
    assertEquals(0, result.bytesProduced());
    assertEquals(HandshakeStatus.NEED_WRAP, result.getHandshakeStatus());
    netS2C.clear();

    // do the needed WRAP of empty buffer
    result = client.wrap(BufferUtil.EMPTY_BUFFER, netC2S);
    // unwrap is a noop
    assertEquals(SSLEngineResult.Status.OK, result.getStatus());
    assertEquals(0, result.bytesConsumed());
    assertThat(result.bytesProduced(), greaterThan(0));
    assertEquals(HandshakeStatus.NEED_UNWRAP, result.getHandshakeStatus());
    netC2S.flip();
    assertEquals(netC2S.remaining(), result.bytesProduced());

    // start the server
    server.setUseClientMode(false);
    server.beginHandshake();
    Assert.assertEquals(HandshakeStatus.NEED_UNWRAP, server.getHandshakeStatus());

    // what if we try a needless wrap?
    serverOut.put(BufferUtil.toBuffer("Hello World"));
    serverOut.flip();
    result = server.wrap(serverOut, netS2C);
    // wrap is a noop
    assertEquals(SSLEngineResult.Status.OK, result.getStatus());
    assertEquals(0, result.bytesConsumed());
    assertEquals(0, result.bytesProduced());
    assertEquals(HandshakeStatus.NEED_UNWRAP, result.getHandshakeStatus());

    // Do the needed unwrap, to an empty buffer
    result = server.unwrap(netC2S, BufferUtil.EMPTY_BUFFER);
    assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus());
    assertEquals(0, result.bytesConsumed());
    assertEquals(0, result.bytesProduced());
    assertEquals(HandshakeStatus.NEED_UNWRAP, result.getHandshakeStatus());

    // Do the needed unwrap, to a full buffer
    serverIn.position(serverIn.limit());
    result = server.unwrap(netC2S, serverIn);
    assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus());
    assertEquals(0, result.bytesConsumed());
    assertEquals(0, result.bytesProduced());
    assertEquals(HandshakeStatus.NEED_UNWRAP, result.getHandshakeStatus());

    // Do the needed unwrap, to an empty buffer
    serverIn.clear();
    result = server.unwrap(netC2S, serverIn);
    assertEquals(SSLEngineResult.Status.OK, result.getStatus());
    assertThat(result.bytesConsumed(), greaterThan(0));
    assertEquals(0, result.bytesProduced());
    assertEquals(HandshakeStatus.NEED_TASK, result.getHandshakeStatus());

    server.getDelegatedTask().run();

    assertEquals(HandshakeStatus.NEED_WRAP, server.getHandshakeStatus());
  }