SSL refactorings.

This commit is contained in:
Simone Bordet 2012-05-11 10:19:47 +02:00
parent 7f291e8831
commit 9e8b2f1aad
14 changed files with 998 additions and 60 deletions

View File

@ -76,14 +76,14 @@ public abstract class AbstractAsyncConnection implements AsyncConnection
else else
_endp.shutdownOutput(); _endp.shutdownOutput();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public void scheduleOnReadable() public void scheduleOnReadable()
{ {
if (_readInterested.compareAndSet(false,true)) if (_readInterested.compareAndSet(false,true))
getEndPoint().readable(null,_readCallback); getEndPoint().readable(null,_readCallback);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public void onReadFail(Throwable cause) public void onReadFail(Throwable cause)
{ {

View File

@ -0,0 +1,19 @@
// ========================================================================
// Copyright (c) 2012 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.ssl;
enum ReadState
{
HANDSHAKING, HANDSHAKEN, IDLE, UNDERFLOW, DECRYPTED, CLOSED
}

View File

@ -0,0 +1,265 @@
package org.eclipse.jetty.io.ssl;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public abstract class SSLMachine
{
private static final Logger logger = Log.getLogger(SslConnection.class);
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
private final Object wrapLock = new Object();
private final SSLEngine engine;
private boolean handshaken;
private boolean remoteClosed;
public SSLMachine(SSLEngine engine)
{
this.engine = engine;
}
public ReadState decrypt(ByteBuffer netInput, ByteBuffer appInput) throws SSLException
{
while (true)
{
SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
logger.debug("Reading from {}, handshake status: {}", netInput, handshakeStatus);
switch (handshakeStatus)
{
case NEED_UNWRAP:
{
ReadState result = unwrap(netInput, appInput);
if (result == null)
break;
return result;
}
case NEED_TASK:
{
executeTasks();
break;
}
case NEED_WRAP:
{
writeForDecrypt(EMPTY_BUFFER);
break;
}
case NOT_HANDSHAKING:
{
ReadState result = unwrap(netInput, appInput);
if (result == null)
break;
return result;
}
default:
throw new IllegalStateException();
}
}
}
public WriteState encrypt(ByteBuffer appOutput, ByteBuffer netOutput) throws SSLException
{
return wrap(appOutput, netOutput);
}
protected abstract void writeForDecrypt(ByteBuffer appOutput);
private ReadState unwrap(ByteBuffer netInput, ByteBuffer appInput) throws SSLException
{
boolean decrypted = false;
while (netInput.hasRemaining())
{
logger.debug("Decrypting from {} to {}", netInput, appInput);
SSLEngineResult result = unwrap(engine, netInput, appInput);
logger.debug("Decrypted from {} to {}, result {}", netInput, appInput, result);
switch (result.getStatus())
{
case OK:
{
SSLEngineResult.HandshakeStatus handshakeStatus = result.getHandshakeStatus();
if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED)
{
if (engine.getUseClientMode())
{
logger.debug("Handshake finished (client), new SSL session");
handshaken = true;
return ReadState.HANDSHAKEN;
}
else
{
if (handshaken)
{
logger.debug("Rehandshake finished (server)");
}
else
{
logger.debug("Handshake finished (server), cached SSL session");
handshaken = true;
return ReadState.HANDSHAKEN;
}
}
}
if (result.bytesProduced() > 0)
{
decrypted = true;
continue;
}
if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP)
continue;
if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED ||
handshakeStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
break;
return null;
}
case BUFFER_UNDERFLOW:
{
return decrypted ? ReadState.DECRYPTED : ReadState.UNDERFLOW;
}
case CLOSED:
{
// We have read the SSL close message and updated the SSLEngine
logger.debug("Close alert received from remote peer");
remoteClosed = true;
return decrypted ? ReadState.DECRYPTED : ReadState.CLOSED;
}
default:
throw new IllegalStateException();
}
}
SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
return ReadState.UNDERFLOW;
return null;
}
private SSLEngineResult unwrap(SSLEngine engine, ByteBuffer netInput, ByteBuffer appInput) throws SSLException
{
int position = BufferUtil.flipToFill(appInput);
try
{
return engine.unwrap(netInput, appInput);
}
finally
{
BufferUtil.flipToFlush(appInput, position);
}
}
private void executeTasks()
{
Runnable task;
while ((task = engine.getDelegatedTask()) != null)
{
task.run();
logger.debug("Executed task: {}", task);
}
}
private WriteState wrap(ByteBuffer appOutput, ByteBuffer netOutput) throws SSLException
{
while (true)
{
// Locking is important because application code may call handshake() (to rehandshake)
// followed by encrypt(). In this case, the handshake() call may need to wrap, then
// unwrap and then wrap again to perform the handshake, and the second wrap may be
// concurrent with the wrap triggered by encrypt(), which would corrupt SSLEngine.
// It is also important that wrap and write are atomic, because SSL packets needs to
// be ordered (and therefore the packet created first must be written before the packet
// created second).
SSLEngineResult result;
synchronized (wrapLock)
{
logger.debug("Encrypting from {} to {}", appOutput, netOutput);
result = wrap(engine, appOutput, netOutput);
logger.debug("Encrypted from {} to {}, result: {}", appOutput, netOutput, result);
/*
if (result.bytesProduced() > 0)
{
try
{
netOutput.flip();
write(netOutput);
netOutput.clear();
}
catch (RuntimeIOException x)
{
// If we try to write the SSL close message but we cannot
// because the other peer has already closed the connection,
// then ignore the exception and continue
if (result.getStatus() != SSLEngineResult.Status.CLOSED)
throw x;
}
}
*/
}
if (result.getStatus() == SSLEngineResult.Status.CLOSED)
return WriteState.CLOSED;
SSLEngineResult.HandshakeStatus handshakeStatus = result.getHandshakeStatus();
if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP)
continue;
if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED)
{
if (engine.getUseClientMode())
{
if (handshaken)
{
logger.debug("Rehandshake finished (client)");
}
else
{
logger.debug("Handshake finished (client), cached SSL session");
handshaken = true;
return WriteState.HANDSHAKEN;
}
}
else
{
logger.debug("Handshake finished (server), new SSL session");
assert !appOutput.hasRemaining();
handshaken = true;
return WriteState.HANDSHAKEN;
}
}
if (!appOutput.hasRemaining())
return null;
}
}
private SSLEngineResult wrap(SSLEngine engine, ByteBuffer appOutput, ByteBuffer netOutput) throws SSLException
{
int position = BufferUtil.flipToFill(netOutput);
try
{
return engine.wrap(appOutput, netOutput);
}
finally
{
BufferUtil.flipToFlush(netOutput, position);
}
}
public boolean isRemoteClosed()
{
return remoteClosed;
}
public void close()
{
}
}

View File

@ -0,0 +1,624 @@
// ========================================================================
// Copyright (c) 2004-2011 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.ssl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import org.eclipse.jetty.io.AbstractAsyncConnection;
import org.eclipse.jetty.io.AbstractEndPoint;
import org.eclipse.jetty.io.AsyncConnection;
import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* An AsyncConnection that acts as an interceptor between and EndPoint and another
* Connection, that implements TLS encryption using an {@link SSLEngine}.
* <p>
* The connector uses an {@link EndPoint} (like {@link SelectChannelEndPoint}) as
* it's source/sink of encrypted data. It then provides {@link #getAppEndPoint()} to
* expose a source/sink of unencrypted data to another connection (eg HttpConnection).
*/
public class SslConnection extends AbstractAsyncConnection
{
private static final Logger logger = Log.getLogger(SslConnection.class);
private final ByteBufferPool byteBufferPool;
private final SSLEngine sslEngine;
private final SSLMachine sslMachine;
private final AsyncEndPoint appEndPoint;
private boolean direct = false;
private ReadState readState = ReadState.HANDSHAKING;
private WriteState writeState = WriteState.HANDSHAKING;
private ByteBuffer appInput;
private ByteBuffer netInput;
private ByteBuffer netOutput;
private Callback appReader;
private Object readContext;
public SslConnection(ByteBufferPool byteBufferPool, Executor executor, AsyncEndPoint endPoint, SSLEngine sslEngine)
{
super(endPoint, executor);
this.byteBufferPool = byteBufferPool;
this.sslEngine = sslEngine;
this.sslMachine = new ConnectionSSLMachine(sslEngine);
this.appEndPoint = new ApplicationEndPoint();
}
public SSLEngine getSSLEngine()
{
return sslEngine;
}
@Override
public void onOpen()
{
try
{
super.onOpen();
scheduleOnReadable();
sslEngine.beginHandshake();
}
catch (SSLException x)
{
throw new RuntimeIOException(x);
}
}
public AsyncEndPoint getAppEndPoint()
{
return appEndPoint;
}
private void updateReadState(ReadState newReadState)
{
ReadState oldReadState = readState;
switch (oldReadState)
{
case HANDSHAKING:
{
if (newReadState != ReadState.HANDSHAKEN)
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
case HANDSHAKEN:
{
switch (newReadState)
{
case IDLE:
{
if (BufferUtil.hasContent(netInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
case UNDERFLOW:
{
if (!BufferUtil.hasContent(netInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
case DECRYPTED:
{
if (!BufferUtil.hasContent(appInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
case CLOSED:
{
if (BufferUtil.hasContent(appInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
default:
{
throw wrongReadStateUpdate(oldReadState, newReadState);
}
}
}
case IDLE:
{
switch (newReadState)
{
case UNDERFLOW:
{
if (!BufferUtil.hasContent(netInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
case DECRYPTED:
{
if (!BufferUtil.hasContent(appInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
case CLOSED:
{
if (BufferUtil.hasContent(appInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
default:
{
throw wrongReadStateUpdate(oldReadState, newReadState);
}
}
}
case DECRYPTED:
{
switch (newReadState)
{
case IDLE:
{
if (BufferUtil.hasContent(netInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
case UNDERFLOW:
{
if (!BufferUtil.hasContent(netInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
case CLOSED:
{
if (BufferUtil.hasContent(appInput))
throw wrongReadStateUpdate(oldReadState, newReadState);
readState = newReadState;
break;
}
default:
{
throw wrongReadStateUpdate(oldReadState, newReadState);
}
}
}
default:
{
throw wrongReadStateUpdate(oldReadState, newReadState);
}
}
}
private IllegalStateException wrongReadStateUpdate(ReadState oldReadState, ReadState newReadState)
{
String message = String.format("Invalid read state update: %s => %s", oldReadState, newReadState);
return new IllegalStateException(message);
}
@Override
public void onReadable()
{
if (appInput != null)
throw new IllegalStateException();
switch (readState)
{
case HANDSHAKING:
case IDLE:
{
if (netInput != null)
throw new IllegalStateException();
netInput = byteBufferPool.acquire(sslEngine.getSession().getPacketBufferSize(), direct);
appInput = byteBufferPool.acquire(sslEngine.getSession().getApplicationBufferSize(), false);
break;
}
case UNDERFLOW:
{
if (netInput == null)
throw new IllegalStateException();
BufferUtil.compact(netInput);
break;
}
default:
{
throw new IllegalStateException("Unexpected read state " + readState);
}
}
AsyncEndPoint endPoint = getEndPoint();
try
{
while (true) // TODO: writes can close the connection, check that also ?
{
BufferUtil.compact(netInput);
int filled = endPoint.fill(netInput);
if (filled == 0)
{
scheduleOnReadable();
break;
}
else if (filled < 0)
{
updateReadState(ReadState.CLOSED);
sslEngine.closeInbound();
break;
}
else if (filled > 0)
{
boolean readMore = decrypt();
if (!readMore)
break;
}
}
}
catch (IOException x)
{
endPoint.close();
}
}
private boolean decrypt() throws SSLException
{
while (true)
{
updateReadState(sslMachine.decrypt(netInput, appInput));
switch (readState)
{
case UNDERFLOW:
{
return true;
}
case HANDSHAKEN:
{
getAppEndPoint().getAsyncConnection().onOpen();
if (!netInput.hasRemaining())
{
updateReadState(ReadState.IDLE);
return true;
}
break;
}
case DECRYPTED:
{
appReader.completed(readContext);
return false;
}
case CLOSED:
{
appReader.completed(readContext);
return false;
}
default:
{
throw new IllegalStateException("Unexpected read state " + readState);
}
}
}
}
public void setAllowRenegotiate(boolean allowRenegotiate)
{
// TODO
}
public class ApplicationEndPoint extends AbstractEndPoint implements AsyncEndPoint
{
private final AtomicBoolean writing = new AtomicBoolean();
private boolean oshut;
private Object context;
private Callback callback;
private ByteBuffer[] buffers;
private AsyncConnection connection;
public ApplicationEndPoint()
{
super(getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
}
@Override
public <C> void readable(C context, Callback<C> callback) throws IllegalStateException
{
if (appReader != null)
throw new ReadPendingException();
switch (readState)
{
case IDLE:
{
if (BufferUtil.hasContent(netInput))
throw new IllegalStateException();
appReader = callback;
readContext = context;
scheduleOnReadable();
break;
}
case UNDERFLOW:
{
if (!BufferUtil.hasContent(netInput))
throw new IllegalStateException();
appReader = callback;
readContext = context;
scheduleOnReadable();
break;
}
case DECRYPTED:
{
if (!BufferUtil.hasContent(appInput))
throw new IllegalStateException();
callback.completed(context);
break;
}
case CLOSED:
{
if (BufferUtil.hasContent(appInput))
throw new IllegalStateException();
callback.completed(context);
break;
}
default:
{
throw new IllegalStateException("Unexpected read state " + readState);
}
}
}
@Override
public int fill(ByteBuffer buffer) throws IOException
{
switch (readState)
{
case IDLE:
case UNDERFLOW:
{
return 0;
}
case DECRYPTED:
{
if (!BufferUtil.hasContent(appInput))
throw new IllegalStateException();
int filled = BufferUtil.append(appInput, buffer);
if (!BufferUtil.hasContent(appInput))
{
byteBufferPool.release(appInput);
appInput = null;
updateReadState(BufferUtil.hasContent(netInput) ? ReadState.UNDERFLOW :
sslMachine.isRemoteClosed() ? ReadState.CLOSED : ReadState.IDLE);
}
return filled;
}
case CLOSED:
{
return -1;
}
default:
{
throw new IllegalStateException("Unexpected read state " + readState);
}
}
}
@Override
public void shutdownOutput()
{
oshut = true;
sslMachine.close();
}
@Override
public boolean isOutputShutdown()
{
return oshut;
}
@Override
public void close()
{
getEndPoint().close();
}
@Override
public boolean isOpen()
{
return getEndPoint().isOpen();
}
@Override
public Object getTransport()
{
return getEndPoint();
}
@Override
public boolean isInputShutdown()
{
return sslMachine.isRemoteClosed();
}
@Override
public int flush(ByteBuffer... appOutputs) throws IOException
{
switch (writeState)
{
case HANDSHAKING:
{
if (netOutput != null)
throw new IllegalStateException();
netOutput = byteBufferPool.acquire(sslEngine.getSession().getPacketBufferSize(), direct);
break;
}
default:
{
throw new IllegalStateException("Unexpected write state " + readState);
}
}
ByteBuffer appOutput = appOutputs[0];
if (!appOutput.hasRemaining() && appOutputs.length > 1)
{
for (int i = 1; i < appOutputs.length; ++i)
{
if (appOutputs[i].hasRemaining())
{
appOutput = appOutputs[i];
break;
}
}
}
int remaining = appOutput.remaining();
sslMachine.encrypt(appOutput, netOutput);
int result = remaining - appOutput.remaining();
getEndPoint().write(null, new Callback<Object>()
{
@Override
public void completed(Object context)
{
completeWrite();
}
@Override
public void failed(Object context, Throwable x)
{
// TODO
}
}, netOutput);
return result;
}
@Override
public <C> void write(C context, Callback<C> callback, ByteBuffer... buffers) throws IllegalStateException
{
if (!writing.compareAndSet(false, true))
throw new WritePendingException();
boolean writePending = false;
try
{
flush(buffers);
for (ByteBuffer buffer : buffers)
{
if (buffer.hasRemaining())
{
this.context = context;
this.callback = callback;
this.buffers = buffers;
writePending = true;
return;
}
}
callback.completed(context);
}
catch (IOException x)
{
callback.failed(context, x);
}
finally
{
writing.set(writePending);
}
}
private void completeWrite()
{
if (buffers == null)
return;
try
{
flush(buffers);
for (ByteBuffer buffer : buffers)
{
if (buffer.hasRemaining())
return;
}
callback.completed(context);
}
catch (IOException x)
{
callback.failed(context, x);
}
finally
{
context = null;
callback = null;
buffers = null;
}
}
@Override
public void setCheckForIdle(boolean check)
{
getEndPoint().setCheckForIdle(check);
}
@Override
public boolean isCheckForIdle()
{
return getEndPoint().isCheckForIdle();
}
@Override
public AsyncConnection getAsyncConnection()
{
return connection;
}
@Override
public void setAsyncConnection(AsyncConnection connection)
{
this.connection = connection;
}
}
private class ConnectionSSLMachine extends SSLMachine
{
private ConnectionSSLMachine(SSLEngine engine)
{
super(engine);
}
@Override
protected void writeForDecrypt(ByteBuffer appOutput)
{
AsyncEndPoint endPoint = getAppEndPoint();
try
{
endPoint.flush(appOutput);
}
catch (IOException x)
{
endPoint.close();
}
}
}
}

View File

@ -11,7 +11,7 @@
// You may elect to redistribute this code under either of these licenses. // You may elect to redistribute this code under either of these licenses.
// ======================================================================== // ========================================================================
package org.eclipse.jetty.io; package org.eclipse.jetty.io.ssl;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -25,6 +25,12 @@ import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import org.eclipse.jetty.io.AbstractAsyncConnection;
import org.eclipse.jetty.io.AbstractEndPoint;
import org.eclipse.jetty.io.AsyncConnection;
import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
@ -39,7 +45,7 @@ import org.eclipse.jetty.util.log.Logger;
* it's source/sink of encrypted data. It then provides {@link #getAppEndPoint()} to * it's source/sink of encrypted data. It then provides {@link #getAppEndPoint()} to
* expose a source/sink of unencrypted data to another connection (eg HttpConnection). * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
*/ */
public class SslConnection extends AbstractAsyncConnection public class SslConnectionOld extends AbstractAsyncConnection
{ {
static final Logger LOG = Log.getLogger("org.eclipse.jetty.io.ssl"); static final Logger LOG = Log.getLogger("org.eclipse.jetty.io.ssl");
@ -102,16 +108,17 @@ public class SslConnection extends AbstractAsyncConnection
_outNet=BufferUtil.allocateDirect(packetSize); _outNet=BufferUtil.allocateDirect(packetSize);
_inApp=BufferUtil.allocate(appSize); _inApp=BufferUtil.allocate(appSize);
} }
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public SslConnection(SSLEngine engine,AsyncEndPoint endp,Executor executor) public SslConnectionOld(SSLEngine engine,AsyncEndPoint endp,Executor executor)
{ {
this(engine,endp,System.currentTimeMillis(),executor); this(engine,endp,System.currentTimeMillis(),executor);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public SslConnection(SSLEngine engine,AsyncEndPoint endp, long timeStamp,Executor executor) public SslConnectionOld(SSLEngine engine,AsyncEndPoint endp, long timeStamp,Executor executor)
{ {
super(endp, executor); super(endp, executor);
_engine=engine; _engine=engine;

View File

@ -0,0 +1,19 @@
// ========================================================================
// Copyright (c) 2012 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.ssl;
enum WriteState
{
HANDSHAKING, HANDSHAKEN, IDLE, ENCRYPTED, CLOSED
}

View File

@ -10,9 +10,9 @@ import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLSocket; 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.MavenTestingUtils;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.Assert; import org.junit.Assert;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Ignore; import org.junit.Ignore;
@ -22,7 +22,8 @@ import org.junit.Test;
@Ignore @Ignore
public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
{ {
static SslContextFactory __sslCtxFactory=new SslContextFactory(); private static SslContextFactory __sslCtxFactory=new SslContextFactory();
private static ByteBufferPool __byteBufferPool = new StandardByteBufferPool();
@BeforeClass @BeforeClass
public static void initSslEngine() throws Exception public static void initSslEngine() throws Exception
@ -47,11 +48,11 @@ public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
{ {
SSLEngine engine = __sslCtxFactory.newSslEngine(); SSLEngine engine = __sslCtxFactory.newSslEngine();
engine.setUseClientMode(false); engine.setUseClientMode(false);
SslConnection connection = new SslConnection(engine,endpoint,_threadPool); SslConnection sslConnection = new SslConnection(__byteBufferPool, _threadPool, endpoint, engine);
AsyncConnection delegate = super.newConnection(channel,connection.getAppEndPoint()); AsyncConnection appConnection = super.newConnection(channel,sslConnection.getAppEndPoint());
connection.getAppEndPoint().setAsyncConnection(delegate); sslConnection.getAppEndPoint().setAsyncConnection(appConnection);
return connection; return sslConnection;
} }
@Test @Test
@ -172,8 +173,6 @@ public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
Assert.assertEquals("HelloWorld",reply); Assert.assertEquals("HelloWorld",reply);
SslConnection.LOG.info("javax.net.ssl.SSLException: Inbound closed before... Expected as next line!");
if (debug) System.err.println("\nSudden Death");
client.socket().shutdownOutput(); client.socket().shutdownOutput();
filled=client.read(sslIn); filled=client.read(sslIn);

View File

@ -35,6 +35,7 @@ public class SelectChannelEndPointTest
protected volatile AsyncEndPoint _lastEndp; protected volatile AsyncEndPoint _lastEndp;
protected ServerSocketChannel _connector; protected ServerSocketChannel _connector;
protected QueuedThreadPool _threadPool = new QueuedThreadPool(); protected QueuedThreadPool _threadPool = new QueuedThreadPool();
private int maxIdleTimeout = 600000; // TODO: use smaller value
protected SelectorManager _manager = new SelectorManager() protected SelectorManager _manager = new SelectorManager()
{ {
@Override @Override
@ -51,6 +52,7 @@ public class SelectChannelEndPointTest
@Override @Override
protected void endPointOpened(AsyncEndPoint endpoint) protected void endPointOpened(AsyncEndPoint endpoint)
{ {
endpoint.getAsyncConnection().onOpen();
} }
@Override @Override
@ -67,7 +69,7 @@ public class SelectChannelEndPointTest
@Override @Override
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
{ {
SelectChannelEndPoint endp = new SelectChannelEndPoint(channel,selectSet,key,2000); SelectChannelEndPoint endp = new SelectChannelEndPoint(channel,selectSet,key,maxIdleTimeout);
endp.setAsyncConnection(selectSet.getManager().newConnection(channel,endp, key.attachment())); endp.setAsyncConnection(selectSet.getManager().newConnection(channel,endp, key.attachment()));
_lastEndp=endp; _lastEndp=endp;
return endp; return endp;
@ -104,9 +106,7 @@ public class SelectChannelEndPointTest
protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint) protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint)
{ {
AbstractAsyncConnection connection = new TestConnection(endpoint); return new TestConnection(endpoint);
connection.scheduleOnReadable();
return connection;
} }
public class TestConnection extends AbstractAsyncConnection public class TestConnection extends AbstractAsyncConnection
@ -120,6 +120,12 @@ public class SelectChannelEndPointTest
super(endp,_threadPool); super(endp,_threadPool);
} }
@Override
public void onOpen()
{
scheduleOnReadable();
}
@Override @Override
public synchronized void onReadable() public synchronized void onReadable()
{ {
@ -233,7 +239,7 @@ public class SelectChannelEndPointTest
{ {
Socket client = newClient(); Socket client = newClient();
client.setSoTimeout(60000); client.setSoTimeout(600000); // TODO: restore to smaller value
SocketChannel server = _connector.accept(); SocketChannel server = _connector.accept();
server.configureBlocking(false); server.configureBlocking(false);
@ -356,7 +362,7 @@ public class SelectChannelEndPointTest
OutputStream clientOutputStream = client.getOutputStream(); OutputStream clientOutputStream = client.getOutputStream();
InputStream clientInputStream = client.getInputStream(); InputStream clientInputStream = client.getInputStream();
int specifiedTimeout = SslConnection.LOG.isDebugEnabled()?2000:400; int specifiedTimeout = 2000;
client.setSoTimeout(specifiedTimeout); client.setSoTimeout(specifiedTimeout);
// Write 8 and cause block waiting for 10 // Write 8 and cause block waiting for 10

View File

@ -3,13 +3,11 @@ package org.eclipse.jetty.server.ssl;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
@ -55,7 +53,7 @@ public class SslCertificates
return null; return null;
} }
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
@ -76,16 +74,14 @@ public class SslCertificates
* client, the next is the one used to authenticate the first, and so on. * client, the next is the one used to authenticate the first, and so on.
* </li> * </li>
* </ul> * </ul>
* *
* @param endpoint
* The Socket the request arrived on. This should be a
* {@link SocketEndPoint} wrapping a {@link SSLSocket}.
* @param request * @param request
* HttpRequest to be customised. * HttpRequest to be customised.
*/ */
public static void customize(SSLSession sslSession, EndPoint endpoint, Request request) throws IOException public static void customize(SSLEngine sslEngine, Request request) throws IOException
{ {
request.setScheme(HttpScheme.HTTPS.asString()); request.setScheme(HttpScheme.HTTPS.asString());
SSLSession sslSession = sslEngine.getSession();
try try
{ {
@ -153,7 +149,7 @@ public class SslCertificates
{ {
return _keySize; return _keySize;
} }
String getIdStr() String getIdStr()
{ {
return _idStr; return _idStr;

View File

@ -15,18 +15,15 @@ package org.eclipse.jetty.server.ssl;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.AsyncConnection; import org.eclipse.jetty.io.AsyncConnection;
import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.io.SslConnection; import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.component.AggregateLifeCycle;
@ -51,7 +48,7 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Construct with explicit SslContextFactory. /** Construct with explicit SslContextFactory.
* The SslContextFactory passed is added via {@link #addBean(Object)} so that * The SslContextFactory passed is added via {@link #addBean(Object)} so that
* it's lifecycle may be managed with {@link AggregateLifeCycle}. * it's lifecycle may be managed with {@link AggregateLifeCycle}.
* @param sslContextFactory * @param sslContextFactory
*/ */
@ -82,9 +79,6 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements
* </li> * </li>
* </ul> * </ul>
* *
* @param endpoint
* The Socket the request arrived on. This should be a
* {@link SocketEndPoint} wrapping a {@link SSLSocket}.
* @param request * @param request
* HttpRequest to be customised. * HttpRequest to be customised.
*/ */
@ -94,12 +88,9 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements
request.setScheme(HttpScheme.HTTPS.asString()); request.setScheme(HttpScheme.HTTPS.asString());
super.customize(request); super.customize(request);
EndPoint endpoint = request.getHttpChannel().getConnection().getEndPoint(); SslConnection sslConnection = (SslConnection)request.getHttpChannel().getConnection();
SslConnection.AppEndPoint sslEndpoint=(SslConnection.AppEndPoint)endpoint; SSLEngine sslEngine=sslConnection.getSSLEngine();
SSLEngine sslEngine=sslEndpoint.getSslEngine(); SslCertificates.customize(sslEngine,request);
SSLSession sslSession=sslEngine.getSession();
SslCertificates.customize(sslSession,endpoint,request);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -563,7 +554,7 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements
protected SslConnection newSslConnection(AsyncEndPoint endpoint, SSLEngine engine) protected SslConnection newSslConnection(AsyncEndPoint endpoint, SSLEngine engine)
{ {
return new SslConnection(engine, endpoint,findExecutor()); return new SslConnection(getByteBufferPool(), findExecutor(), endpoint, engine);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -624,5 +615,4 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements
{ {
super.doStop(); super.doStop();
} }
} }

View File

@ -13,10 +13,6 @@
package org.eclipse.jetty.server; package org.eclipse.jetty.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.matchers.JUnitMatchers.containsString;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -24,19 +20,22 @@ import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.util.concurrent.Exchanger; import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.SslConnection; import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.matchers.JUnitMatchers.containsString;
public abstract class ConnectorTimeoutTest extends HttpServerTestFixture public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
{ {
protected static final int MAX_IDLE_TIME=250; protected static final int MAX_IDLE_TIME=250;
@ -149,8 +148,8 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
// Get the server side endpoint // Get the server side endpoint
EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS); EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS);
if (endp instanceof SslConnection.AppEndPoint) if (endp instanceof SslConnection.ApplicationEndPoint)
endp=((SslConnection.AppEndPoint)endp).getEndpoint(); endp=((SslConnection.ApplicationEndPoint)endp).getEndpoint();
// read the response // read the response
String result=IO.toString(is); String result=IO.toString(is);

View File

@ -53,15 +53,29 @@ public class SPDYAsyncConnection extends AbstractAsyncConnection implements Cont
BufferUtil.clear(buffer); BufferUtil.clear(buffer);
read(buffer); read(buffer);
bufferPool.release(buffer); bufferPool.release(buffer);
scheduleOnReadable();
} }
protected void read(ByteBuffer buffer) protected void read(ByteBuffer buffer)
{ {
AsyncEndPoint endPoint = getEndPoint(); AsyncEndPoint endPoint = getEndPoint();
int filled = fill(endPoint, buffer); while (true)
if (filled < 0) {
close(false); int filled = fill(endPoint, buffer);
parser.parse(buffer); if (filled == 0)
{
break;
}
else if (filled < 0)
{
close(false);
break;
}
else
{
parser.parse(buffer);
}
}
} }
private int fill(AsyncEndPoint endPoint, ByteBuffer buffer) private int fill(AsyncEndPoint endPoint, ByteBuffer buffer)

View File

@ -47,8 +47,8 @@ import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.SelectChannelEndPoint; import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.SelectorManager; import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.SslConnection;
import org.eclipse.jetty.io.StandardByteBufferPool; import org.eclipse.jetty.io.StandardByteBufferPool;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.npn.NextProtoNego; import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.spdy.api.Session; import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener; import org.eclipse.jetty.spdy.api.SessionFrameListener;

View File

@ -36,8 +36,8 @@ import javax.net.ssl.SSLException;
import org.eclipse.jetty.io.AsyncConnection; import org.eclipse.jetty.io.AsyncConnection;
import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.SslConnection;
import org.eclipse.jetty.io.StandardByteBufferPool; import org.eclipse.jetty.io.StandardByteBufferPool;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.npn.NextProtoNego; import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.spdy.api.SPDY; import org.eclipse.jetty.spdy.api.SPDY;