/* * JBoss, Home of Professional Open Source. * * Copyright 2013 Red Hat, Inc. and/or its affiliates, and individual * contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xnio.ssl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.xnio.ssl.mock.SSLEngineMock.CLOSE_MSG; import static org.xnio.ssl.mock.SSLEngineMock.HANDSHAKE_MSG; import static org.xnio.ssl.mock.SSLEngineMock.HandshakeAction.FINISH; import static org.xnio.ssl.mock.SSLEngineMock.HandshakeAction.NEED_UNWRAP; import static org.xnio.ssl.mock.SSLEngineMock.HandshakeAction.NEED_WRAP; import java.io.IOException; import java.nio.ByteBuffer; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import org.jmock.Expectations; import org.junit.Test; import org.xnio.BufferAllocator; import org.xnio.ByteBufferSlicePool; import org.xnio.ChannelListener; import org.xnio.Options; import org.xnio.Pool; import org.xnio.conduits.ConduitStreamSinkChannel; import org.xnio.conduits.ConduitStreamSourceChannel; import org.xnio.mock.StreamConnectionMock; import org.xnio.ssl.mock.SSLEngineMock.HandshakeAction; /** * Test for SslConnection on start tls mode. * * @author <a href="mailto:frainone@redhat.com">Flavia Rainone</a> */ public class StartTLSConnectionTestCase extends AbstractSslConnectionTest { @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected SslConnection createSslConnection() { final Pool<ByteBuffer> socketBufferPool = new ByteBufferSlicePool(BufferAllocator.BYTE_BUFFER_ALLOCATOR, 17000, 17000 * 16); final Pool<ByteBuffer> applicationBufferPool = new ByteBufferSlicePool(BufferAllocator.BYTE_BUFFER_ALLOCATOR, 17000, 17000 * 16); final StreamConnectionMock connectionMock = new StreamConnectionMock(conduitMock); final SslConnection connection = new JsseSslConnection(connectionMock, engineMock, socketBufferPool, applicationBufferPool); final ChannelListener readListener = context.mock(ChannelListener.class, "read listener"); connection.getSourceChannel().getReadSetter().set(readListener); context.checking(new Expectations() {{ allowing(readListener).handleEvent(with(any(ConduitStreamSinkChannel.class))); allowing(readListener).handleEvent(with(any(ConduitStreamSourceChannel.class))); }}); connection.getSinkChannel().getWriteSetter().set(context.mock(ChannelListener.class, "write listener")); return connection; } @Test public void getSecureOption() throws IOException { assertFalse(connection.getOption(Options.SECURE)); startHandshake(); assertTrue(connection.getOption(Options.SECURE)); } @Test public void getSslSession() throws IOException { assertNull(connection.getSslSession()); startHandshake(); assertNotNull(connection.getSslSession()); } @Test public void testSimpleFlush() throws IOException { // handshake action: NEED_WRAP conduitMock.enableWrites(true); final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("MSG".getBytes("UTF-8")).flip(); assertEquals(3, sinkConduit.write(buffer)); assertWrittenMessage("MSG"); assertFalse(conduitMock.isFlushed()); sinkConduit.flush(); assertTrue(conduitMock.isFlushed()); assertWrittenMessage("MSG"); } @Test public void testFlushWithHandshaking() throws IOException { // handshake action: NEED_WRAP... this hanshake action will be ignored on START_TLS mode engineMock.setHandshakeActions(NEED_WRAP); conduitMock.enableWrites(true); final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("MSG".getBytes("UTF-8")).flip(); assertEquals(3, sinkConduit.write(buffer)); assertWrittenMessage("MSG"); assertFalse(conduitMock.isFlushed()); sinkConduit.flush(); assertTrue(conduitMock.isFlushed()); assertWrittenMessage("MSG"); } @Test public void readNeedsWrapWriteAndReadDisabled() throws IOException { // handshake action: NEED_WRAP engineMock.setHandshakeActions(NEED_WRAP, FINISH); conduitMock.setReadData(CLOSE_MSG); conduitMock.enableReads(false); conduitMock.enableWrites(false); final ByteBuffer buffer = ByteBuffer.allocate(100); assertFalse(conduitMock.isReadAwaken()); sourceConduit.resumeReads(); assertTrue(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadAwaken()); assertFalse(conduitMock.isWriteResumed()); // attempt to read... channel is expected to return 0 as it stumbles upon a NEED_WRAP that cannot be executed assertEquals(0, sourceConduit.read(new ByteBuffer[]{buffer}, 0, 1)); // everything is kept the same assertFalse(conduitMock.isReadResumed()); assertTrue(sourceConduit.isReadResumed()); assertFalse(conduitMock.isWriteResumed()); assertFalse(sinkConduit.isWriteResumed()); // no handshake is performed assertSame(HandshakeStatus.NEED_WRAP, engineMock.getHandshakeStatus()); assertEquals(0, sourceConduit.read(buffer)); assertSame(HandshakeStatus.NEED_WRAP, engineMock.getHandshakeStatus()); conduitMock.enableReads(true); assertEquals(7, sourceConduit.read(buffer)); assertWrittenMessage(new String[0]); // close message is just read as a plain message, as sourceConduit.read is simply delegated to conduitMock assertReadMessage(buffer, CLOSE_MSG); assertSame(HandshakeStatus.NEED_WRAP, engineMock.getHandshakeStatus()); sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); assertTrue(conduitMock.isWriteShutdown()); sourceConduit.terminateReads(); assertTrue(conduitMock.isReadShutdown()); conduitMock.enableWrites(true); sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); assertTrue(conduitMock.isWriteShutdown()); assertSame(HandshakeStatus.NEED_WRAP, engineMock.getHandshakeStatus()); assertWrittenMessage(new String[0]); assertTrue(conduitMock.isFlushed()); } @Test public void writeNeedsUnwrapReadAndFlushDisabled () throws IOException { // handshake action: NEED_WRAP engineMock.setHandshakeActions(NEED_UNWRAP, FINISH); conduitMock.setReadData(HANDSHAKE_MSG); conduitMock.enableReads(false); conduitMock.enableWrites(true); conduitMock.enableFlush(false); final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("MSG".getBytes("UTF-8")).flip(); assertFalse(conduitMock.isReadResumed()); assertFalse(conduitMock.isWriteResumed()); // attempt to write... channel is expected to write because, on STARTLS mode, it wil simply delegate the // write request to conduitMock assertEquals(3, sinkConduit.write(buffer)); // no read/write operation has been resumed either assertFalse(conduitMock.isReadResumed()); assertFalse(conduitMock.isWriteResumed()); // the first handshake action is as before, nothing has changed assertSame(HandshakeStatus.NEED_UNWRAP, engineMock.getHandshakeStatus()); assertFalse(sinkConduit.flush()); assertWrittenMessage("MSG"); assertFalse(conduitMock.isFlushed()); conduitMock.enableFlush(true); assertTrue(sinkConduit.flush()); assertWrittenMessage("MSG"); sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); assertWrittenMessage("MSG"); assertTrue(conduitMock.isFlushed()); } @Test public void cantForceResumeReadsOnResumedReadChannel() throws IOException { sourceConduit.resumeReads(); sinkConduit.resumeWrites(); assertTrue(sourceConduit.isReadResumed()); assertTrue(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isReadAwaken()); assertFalse(conduitMock.isWriteAwaken()); engineMock.setHandshakeActions(NEED_UNWRAP); final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("COULDNT WRITE WITHOUT UNWRAP".getBytes("UTF-8")).flip(); assertEquals(28, sinkConduit.write(buffer)); assertWrittenMessage("COULDNT WRITE WITHOUT UNWRAP"); assertTrue(sinkConduit.isWriteResumed()); assertTrue(sourceConduit.isReadResumed()); assertTrue(conduitMock.isWriteResumed()); assertFalse(conduitMock.isReadAwaken()); // everything keeps the same at conduitMock when we try to resume reads sinkConduit.resumeWrites(); assertTrue(sinkConduit.isWriteResumed()); assertTrue(sourceConduit.isReadResumed()); assertTrue(conduitMock.isWriteResumed()); assertFalse(conduitMock.isReadAwaken()); } @Test public void cantForceResumeReadsOnSuspendedReadChannel() throws IOException { // resume writes, reads are suspended sinkConduit.resumeWrites(); assertFalse(sourceConduit.isReadResumed()); assertTrue(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isReadResumed()); assertFalse(conduitMock.isWriteAwaken()); // write needs to unwrap... try to write engineMock.setHandshakeActions(NEED_UNWRAP); final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("COULDNT WRITE WITHOUT UNWRAP".getBytes("UTF-8")).flip(); assertEquals(28, sinkConduit.write(buffer)); assertWrittenMessage("COULDNT WRITE WITHOUT UNWRAP"); // nothing happens with read/write resumed on START_TLS channel assertTrue(sinkConduit.isWriteResumed()); assertFalse(sourceConduit.isReadResumed()); assertTrue(conduitMock.isWriteResumed()); assertFalse(conduitMock.isReadResumed()); assertFalse(conduitMock.isReadAwaken()); // everything keeps the same at conduitMock when we try to resume writes sinkConduit.resumeWrites(); assertTrue(sinkConduit.isWriteResumed()); assertFalse(sourceConduit.isReadResumed()); assertTrue(conduitMock.isWriteResumed()); assertFalse(conduitMock.isReadResumed()); assertFalse(conduitMock.isReadAwaken()); } @Test public void resumeAndSuspendReadsOnNewChannel() throws Exception { // brand newly created sslChannel, isReadable returns aLWAYS and resuming read will awakeReads for conduitMock assertFalse(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadResumed()); sourceConduit.resumeReads(); assertTrue(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadAwaken()); sourceConduit.suspendReads(); assertFalse(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadResumed()); } @Test public void resumeAndSuspendReads() throws IOException { assertEquals(0, sourceConduit.read(ByteBuffer.allocate(5))); // not a brand newly created sslChannel, isReadable returns OKAY and resuming read will just reasumeReads for conduitMock assertFalse(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadResumed()); sourceConduit.resumeReads(); assertTrue(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadAwaken()); assertFalse(conduitMock.isReadResumed()); sourceConduit.suspendReads(); assertFalse(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadResumed()); } @Test public void resumeAndSuspendWritesOnNewChannel() throws Exception { // brand newly created sslChannel, isWritable returns aLWAYS and resuming writes will awakeWrites for conduitMock assertFalse(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isWriteResumed()); sinkConduit.resumeWrites(); assertTrue(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isWriteAwaken()); sinkConduit.suspendWrites(); assertFalse(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isWriteResumed()); } @Test public void resumeAndSuspendWrites() throws Exception { assertEquals(0, sourceConduit.read(ByteBuffer.allocate(5))); // not a brand newly created sslChannel, isWritable returns OKAY and resuming writes will just reasumeWritesfor conduitMock assertFalse(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isWriteResumed()); sinkConduit.resumeWrites(); assertTrue(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isWriteAwaken()); assertTrue(conduitMock.isWriteResumed()); sinkConduit.suspendWrites(); assertFalse(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isWriteResumed()); } @Test public void resumeAndSuspendWritesOnWriteNeedsUnwrapChannel() throws Exception { // create the read needs wrap channel\ engineMock.setHandshakeActions(HandshakeAction.NEED_UNWRAP, HandshakeAction.FINISH); ByteBuffer buffer = ByteBuffer.allocate(5); buffer.put("12345".getBytes("UTF-8")).flip(); // channel manages to write anyway, because we are on START_TLS mode assertEquals(5, sinkConduit.write(new ByteBuffer[]{buffer}, 0, 1)); assertWrittenMessage("12345"); // no read action is forced assertFalse(conduitMock.isReadResumed()); assertFalse(sourceConduit.isReadResumed()); assertFalse(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isWriteResumed()); // try to resume writes sinkConduit.resumeWrites(); assertTrue(sinkConduit.isWriteResumed()); assertTrue(conduitMock.isWriteResumed()); sinkConduit.suspendWrites(); assertFalse(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isWriteResumed()); } @Test public void suspendWritesOnResumedWriteNeedsUnwrapChannel() throws Exception { // resume writes first of all sinkConduit.resumeWrites(); assertTrue(sinkConduit.isWriteResumed()); assertTrue(conduitMock.isWriteResumed()); assertFalse(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadResumed()); // need unwrap is the first handshake action, and conduitMock has read ops disabled engineMock.setHandshakeActions(HandshakeAction.NEED_UNWRAP, HandshakeAction.FINISH); // channel can write regardless of the NEED_UNWRAP handshake action, given START_TLS mode is on ByteBuffer buffer = ByteBuffer.allocate(1); buffer.put((byte) 0).flip(); assertEquals(1, sinkConduit.write(buffer)); assertWrittenMessage("\0"); assertTrue(sinkConduit.isWriteResumed()); assertTrue(conduitMock.isWriteResumed()); assertFalse(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadResumed()); // suspendWrites sinkConduit.suspendWrites(); assertFalse(sinkConduit.isWriteResumed()); assertFalse(conduitMock.isWriteResumed()); assertFalse(sourceConduit.isReadResumed()); assertFalse(conduitMock.isReadResumed()); } @SuppressWarnings("unchecked") @Test public void startTLSWithWriteNeedsUnwrap() throws IOException { //set a handshake listener final ChannelListener<SslConnection> listener = context.mock(ChannelListener.class, "write needs unwrap"); context.checking(new Expectations() {{ atLeast(1).of(listener).handleEvent(connection); }}); connection.getHandshakeSetter().set(listener); // handshake action: NEED_WRAP engineMock.setHandshakeActions(NEED_UNWRAP, FINISH); conduitMock.setReadData(HANDSHAKE_MSG); conduitMock.enableReads(false); conduitMock.enableWrites(false); final ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("MSG".getBytes("UTF-8")).flip(); assertFalse(conduitMock.isReadResumed()); assertFalse(conduitMock.isWriteResumed()); // start tls startHandshake(); // attempt to write... channel is expected to return 0 as it stumbles upon a NEED_UNWRAP that cannot be executed assertEquals(0, sinkConduit.write(buffer)); assertFalse(conduitMock.isReadResumed()); assertFalse(conduitMock.isWriteResumed()); assertSame(HandshakeStatus.NEED_UNWRAP, engineMock.getHandshakeStatus()); assertEquals(0, sinkConduit.write(buffer)); assertSame(HandshakeStatus.NEED_UNWRAP, engineMock.getHandshakeStatus()); conduitMock.enableReads(true); assertEquals(3, sinkConduit.write(buffer)); assertSame(HandshakeStatus.NOT_HANDSHAKING, engineMock.getHandshakeStatus()); assertWrittenMessage(new String[0]); assertFalse(sinkConduit.flush()); assertWrittenMessage(new String[0]); sinkConduit.terminateWrites(); assertFalse(sinkConduit.flush()); assertFalse(conduitMock.isWriteShutdown()); conduitMock.enableWrites(true); assertFalse(conduitMock.isWriteShutdown()); conduitMock.setReadData(CLOSE_MSG); assertTrue(sinkConduit.flush()); assertTrue(conduitMock.isWriteShutdown()); assertTrue(conduitMock.isOpen()); connection.close(); assertFalse(conduitMock.isOpen()); assertTrue(sourceConduit.isReadShutdown()); assertTrue(sinkConduit.isWriteShutdown()); assertTrue(conduitMock.isReadShutdown()); assertTrue(conduitMock.isWriteShutdown()); assertWrittenMessage("MSG", CLOSE_MSG); assertTrue(conduitMock.isFlushed()); } @SuppressWarnings("unchecked") @Test public void startTLSWithReadNeedsWrap() throws IOException { //set a handshake listener final ChannelListener<SslConnection> listener = context.mock(ChannelListener.class, "read needs unwrap"); context.checking(new Expectations() {{ atLeast(1).of(listener).handleEvent(connection); }}); connection.getHandshakeSetter().set(listener); // handshake action: NEED_WRAP engineMock.setHandshakeActions(NEED_WRAP, FINISH); conduitMock.setReadData("MSG"); conduitMock.enableReads(true); conduitMock.enableWrites(false); final ByteBuffer buffer = ByteBuffer.allocate(100); assertFalse(conduitMock.isReadResumed()); assertFalse(conduitMock.isWriteResumed()); // start tls startHandshake(); // attempt to write... channel is expected to return 3 regardless of the fact it cannot flush assertSame(HandshakeStatus.NEED_WRAP, engineMock.getHandshakeStatus()); assertEquals(3, sourceConduit.read(new ByteBuffer[]{buffer}, 0, 1)); assertReadMessage(buffer, "MSG"); assertSame(HandshakeStatus.NOT_HANDSHAKING, engineMock.getHandshakeStatus()); assertWrittenMessage(""); assertFalse(sinkConduit.flush()); // enable writes and hence flush handshake message conduitMock.enableWrites(true); assertTrue(sinkConduit.flush()); assertWrittenMessage(HANDSHAKE_MSG); sourceConduit.terminateReads(); assertFalse(conduitMock.isWriteShutdown()); conduitMock.setReadData(CLOSE_MSG); sinkConduit.terminateWrites(); assertTrue(sinkConduit.flush()); assertTrue(conduitMock.isWriteShutdown()); // channel is already closed assertFalse(conduitMock.isOpen()); assertTrue(sourceConduit.isReadShutdown()); assertTrue(sinkConduit.isWriteShutdown()); assertTrue(conduitMock.isReadShutdown()); assertTrue(conduitMock.isWriteShutdown()); assertWrittenMessage(HANDSHAKE_MSG, CLOSE_MSG); assertTrue(conduitMock.isFlushed()); } private void startHandshake() throws IOException { connection.startHandshake(); // update sinkConduits sinkConduit = connection.getSinkChannel().getConduit(); sourceConduit = connection.getSourceChannel().getConduit(); } }