/** * 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; }
@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); } }
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); } }
@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); } }
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; }
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; }
/** * 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; } }
/** * 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); } }); }
/** * 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); } }
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()); }