| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.io; |
| |
| import static org.hamcrest.Matchers.greaterThan; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertThat; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.Socket; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.SocketChannel; |
| import java.nio.charset.StandardCharsets; |
| |
| import javax.net.ssl.SSLEngine; |
| import javax.net.ssl.SSLEngineResult; |
| import javax.net.ssl.SSLEngineResult.HandshakeStatus; |
| import javax.net.ssl.SSLException; |
| import javax.net.ssl.SSLSocket; |
| |
| import org.eclipse.jetty.io.ssl.SslConnection; |
| import org.eclipse.jetty.toolchain.test.MavenTestingUtils; |
| import org.eclipse.jetty.toolchain.test.annotation.Stress; |
| import org.eclipse.jetty.util.BufferUtil; |
| import org.eclipse.jetty.util.ssl.SslContextFactory; |
| import org.junit.Assert; |
| import org.junit.BeforeClass; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| |
| |
| public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest |
| { |
| private static SslContextFactory __sslCtxFactory=new SslContextFactory(); |
| private static ByteBufferPool __byteBufferPool = new MappedByteBufferPool(); |
| |
| @BeforeClass |
| public static void initSslEngine() throws Exception |
| { |
| File keystore = MavenTestingUtils.getTestResourceFile("keystore"); |
| __sslCtxFactory.setKeyStorePath(keystore.getAbsolutePath()); |
| __sslCtxFactory.setKeyStorePassword("storepwd"); |
| __sslCtxFactory.setKeyManagerPassword("keypwd"); |
| __sslCtxFactory.setEndpointIdentificationAlgorithm(""); |
| __sslCtxFactory.start(); |
| } |
| |
| @Override |
| protected Socket newClient() throws IOException |
| { |
| SSLSocket socket = __sslCtxFactory.newSslSocket(); |
| socket.connect(_connector.socket().getLocalSocketAddress()); |
| return socket; |
| } |
| |
| @Override |
| protected Connection newConnection(SocketChannel channel, EndPoint endpoint) |
| { |
| SSLEngine engine = __sslCtxFactory.newSSLEngine(); |
| engine.setUseClientMode(false); |
| SslConnection sslConnection = new SslConnection(__byteBufferPool, _threadPool, endpoint, engine); |
| sslConnection.setRenegotiationAllowed(__sslCtxFactory.isRenegotiationAllowed()); |
| sslConnection.setRenegotiationLimit(__sslCtxFactory.getRenegotiationLimit()); |
| Connection appConnection = super.newConnection(channel,sslConnection.getDecryptedEndPoint()); |
| sslConnection.getDecryptedEndPoint().setConnection(appConnection); |
| return sslConnection; |
| } |
| |
| @Test |
| @Override |
| public void testEcho() throws Exception |
| { |
| super.testEcho(); |
| } |
| |
| |
| @Ignore // SSL does not do half closes |
| @Override |
| public void testShutdown() throws Exception |
| { |
| } |
| |
| |
| @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); |
| |
| Thread.sleep(100); // TODO This should not be needed |
| Assert.assertFalse(server.isOpen()); |
| } |
| |
| @Test |
| @Override |
| public void testWriteBlocked() throws Exception |
| { |
| super.testWriteBlocked(); |
| } |
| |
| @Override |
| public void testReadBlocked() throws Exception |
| { |
| super.testReadBlocked(); |
| } |
| |
| @Override |
| public void testIdle() throws Exception |
| { |
| super.testIdle(); |
| } |
| |
| @Test |
| @Override |
| @Stress("Requires a relatively idle (network wise) environment") |
| public void testStress() throws Exception |
| { |
| super.testStress(); |
| } |
| |
| @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()); |
| |
| |
| |
| |
| } |
| } |