diff options
Diffstat (limited to 'jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslSelectChannelEndPoint.java')
-rw-r--r-- | jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslSelectChannelEndPoint.java | 926 |
1 files changed, 926 insertions, 0 deletions
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslSelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslSelectChannelEndPoint.java new file mode 100644 index 0000000000..0133c62920 --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslSelectChannelEndPoint.java @@ -0,0 +1,926 @@ +// ======================================================================== +// Copyright (c) 2004-2009 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.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * SslSelectChannelEndPoint + * <p> + * A SelectChannelEndPoint that uses an {@link SSLEngine} to handle an + * SSL connection. + * <p> + * There is a named logger "org.eclipse.jetty.http.ssl" + * </p> + */ +public class SslSelectChannelEndPoint extends SelectChannelEndPoint +{ + static Logger __log = Log.getLogger("org.eclipse.jetty.http.ssl"); + + private static final ByteBuffer[] __NO_BUFFERS={}; + + private final Buffers _buffers; + + private final SSLEngine _engine; + private final SSLSession _session; + private volatile NIOBuffer _inNIOBuffer; + private volatile NIOBuffer _outNIOBuffer; + + private final ByteBuffer[] _gather=new ByteBuffer[2]; + + private boolean _closing=false; + private SSLEngineResult _result; + + private boolean _handshook=false; + private boolean _allowRenegotiate=false; + + private final boolean _debug = __log.isDebugEnabled(); // snapshot debug status for optimizer + + /* ------------------------------------------------------------ */ + public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine) + throws IOException + { + super(channel,selectSet,key); + _buffers=buffers; + + // ssl + _engine=engine; + _session=engine.getSession(); + + if (_debug) __log.debug(_session+" channel="+channel); + } + + /* ------------------------------------------------------------ */ + private void needOutBuffer() + { + if (_outNIOBuffer==null) + { + _outNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize()); + } + } + + /* ------------------------------------------------------------ */ + private void needInBuffer() + { + if (_inNIOBuffer==null) + { + _inNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize()); + } + } + + /* ------------------------------------------------------------ */ + private void freeOutBuffer() + { + if (_outNIOBuffer.length()==0) + { + _buffers.returnBuffer(_outNIOBuffer); + _outNIOBuffer=null; + } + } + + /* ------------------------------------------------------------ */ + private void freeInBuffer() + { + if (_inNIOBuffer.length()==0) + { + _buffers.returnBuffer(_inNIOBuffer); + _inNIOBuffer=null; + } + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL re-negotiation is allowed (default false) + */ + public boolean isAllowRenegotiate() + { + return _allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + /** + * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered + * a vulnerability in SSL/TLS with re-negotiation. If your JVM + * does not have CVE-2009-3555 fixed, then re-negotiation should + * not be allowed. + * @param allowRenegotiate true if re-negotiation is allowed (default false) + */ + public void setAllowRenegotiate(boolean allowRenegotiate) + { + _allowRenegotiate = allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + // TODO get rid of these dumps + public void dump() + { + Log.info(""+_result); + } + /* ------------------------------------------------------------ */ + @Override + public void shutdownOutput() throws IOException + { + // TODO - this really should not be done in a loop here - but with async callbacks. + long end=System.currentTimeMillis()+((SocketChannel)_channel).socket().getSoTimeout(); + try + { + if (isBufferingOutput()) + { + flush(); + while (isOpen() && isBufferingOutput() && System.currentTimeMillis()<end) + { + Thread.sleep(100); // TODO non blocking + flush(); + } + } + + _engine.closeOutbound(); + + loop: while (isOpen() && !(_engine.isInboundDone() && _engine.isOutboundDone()) && System.currentTimeMillis()<end) + { + if (isBufferingOutput()) + { + flush(); + while (isOpen() && isBufferingOutput() && System.currentTimeMillis()<end) + { + Thread.sleep(100); // TODO non blocking + flush(); + } + } + + if (_debug) __log.debug(_session+" closing "+_engine.getHandshakeStatus()); + switch(_engine.getHandshakeStatus()) + { + case FINISHED: + case NOT_HANDSHAKING: + _handshook=true; + break loop; + + case NEED_UNWRAP: + Buffer buffer =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize()); + try + { + ByteBuffer bbuffer = ((NIOBuffer)buffer).getByteBuffer(); + if (!unwrap(bbuffer) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) + { + break loop; + } + } + catch(SSLException e) + { + Log.ignore(e); + } + finally + { + _buffers.returnBuffer(buffer); + } + break; + + case NEED_TASK: + { + Runnable task; + while ((task=_engine.getDelegatedTask())!=null) + { + task.run(); + } + break; + } + + case NEED_WRAP: + { + needOutBuffer(); + ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); + try + { + _outNIOBuffer.compact(); + int put=_outNIOBuffer.putIndex(); + out_buffer.position(put); + _result=null; + _result=_engine.wrap(__NO_BUFFERS,out_buffer); + if (_debug) __log.debug(_session+" close wrap "+_result); + _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); + } + finally + { + out_buffer.position(0); + freeOutBuffer(); + } + + break; + } + } + } + } + catch (InterruptedException e) + { + Log.ignore(e); + } + } + + + /* ------------------------------------------------------------ */ + @Override + public void close() throws IOException + { + try + { + _closing=true; + shutdownOutput(); + } + catch(IOException e) + { + Log.ignore(e); + } + finally + { + super.close(); + if (_inNIOBuffer!=null) + _buffers.returnBuffer(_inNIOBuffer); + if (_outNIOBuffer!=null) + _buffers.returnBuffer(_outNIOBuffer); + } + } + + + + /* ------------------------------------------------------------ */ + /** Fill the buffer with unencrypted bytes. + * Called by a Http Parser when more data is + * needed to continue parsing a request or a response. + */ + @Override + public int fill(Buffer buffer) throws IOException + { + // This end point only works on NIO buffer type (director + // or indirect), so extract the NIO buffer that is wrapped + // by the passed jetty Buffer. + final ByteBuffer bbuf=extractInputBuffer(buffer); + + // remember the original size of the unencrypted buffer + int size=buffer.length(); + + HandshakeStatus initialStatus = _engine.getHandshakeStatus(); + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (bbuf) + { + try + { + // Call the SSLEngine unwrap method to process data in + // the inBuffer. If there is no data in the inBuffer, then + // super.fill is called to read encrypted bytes. + unwrap(bbuf); + + // Loop through the SSL engine state machine + + int wraps=0; + loop: while (true) + { + // If we have encrypted data in output buffer + if (isBufferingOutput()) + { + // we must flush it, as the other end might be + // waiting for that outgoing data before sending + // more incoming data + flush(); + + // If we were unable to flush all the data, then + // we should break the loop and wait for the call + // back to handle when the SelectSet detects that + // the channel is writable again. + if (isBufferingOutput()) + break loop; + } + + // handle the current hand share status + switch(_engine.getHandshakeStatus()) + { + case FINISHED: + case NOT_HANDSHAKING: + // If we are closing, then unwrap must have CLOSED result, + // so return -1 to signal upwards + if (_closing) + return -1; + + // otherwise we break loop with the data we have unwrapped. + break loop; + + case NEED_UNWRAP: + checkRenegotiate(); + // Need more data to be unwrapped so try another call to unwrap + if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) + { + // If the unwrap call did not make any progress and we are still in + // NEED_UNWRAP, then we should break the loop and wait for more data to + // arrive. + break loop; + } + // progress was made so continue the loop. + break; + + case NEED_TASK: + { + // A task needs to be run, so run it! + + Runnable task; + while ((task=_engine.getDelegatedTask())!=null) + { + task.run(); + } + + // Detect SUN JVM Bug!!! + if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && + _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0) + { + // This should be NEED_WRAP + // The fix simply detects the signature of the bug and then close the connection (fail-fast) so that ff3 will delegate to using SSL instead of TLS. + // This is a jvm bug on java1.6 where the SSLEngine expects more data from the initial handshake when the client(ff3-tls) already had given it. + // See http://jira.codehaus.org/browse/JETTY-567 for more details + if (_debug) __log.warn(_session+" JETTY-567"); + return -1; + } + break; + } + + case NEED_WRAP: + { + checkRenegotiate(); + // The SSL needs to send some handshake data to the other side, + // so let fill become a flush for a little bit. + wraps++; + needOutBuffer(); + ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); + synchronized(out_buffer) + { + try + { + // call wrap with empty application buffers, so it can + // generate required handshake messages into _outNIOBuffer + _outNIOBuffer.compact(); + int put=_outNIOBuffer.putIndex(); + out_buffer.position(); + _result=null; + _result=_engine.wrap(__NO_BUFFERS,out_buffer); + if (_debug) __log.debug(_session+" fill wrap "+_result); + switch(_result.getStatus()) + { + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + Log.warn("wrap {}",_result); + case CLOSED: + _closing=true; + } + + _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); + } + finally + { + out_buffer.position(0); + } + } + + // flush the encrypted outNIOBuffer + flush(); + freeOutBuffer(); + + break; + } + } + } + } + catch(SSLException e) + { + Log.warn(e.toString()); + Log.debug(e); + throw e; + } + finally + { + // reset the Buffers + buffer.setPutIndex(bbuf.position()); + bbuf.position(0); + } + + // return the number of unencrypted bytes filled. + int filled=buffer.length()-size; + if (filled>0) + _handshook=true; + return filled; + } + } + + /* ------------------------------------------------------------ */ + @Override + public int flush(Buffer buffer) throws IOException + { + return flush(buffer,null,null); + } + + + /* ------------------------------------------------------------ */ + /* + */ + @Override + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + int consumed=0; + int available=header.length(); + if (buffer!=null) + available+=buffer.length(); + + needOutBuffer(); + ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); + loop: while (true) + { + if (_outNIOBuffer.length()>0) + { + flush(); + if (isBufferingOutput()) + break loop; + } + + switch(_engine.getHandshakeStatus()) + { + case FINISHED: + case NOT_HANDSHAKING: + if (_closing || available==0) + { + if (consumed==0) + consumed= -1; + break loop; + } + + int c; + if (header!=null && header.length()>0) + { + if (buffer!=null && buffer.length()>0) + c=wrap(header,buffer); + else + c=wrap(header); + } + else + c=wrap(buffer); + + + if (c>0) + { + _handshook=true; + consumed+=c; + available-=c; + } + else if (c<0) + { + if (consumed==0) + consumed=-1; + break loop; + } + + break; + + case NEED_UNWRAP: + checkRenegotiate(); + Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize()); + try + { + ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer(); + if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) + { + break loop; + } + } + finally + { + _buffers.returnBuffer(buf); + } + + break; + + case NEED_TASK: + { + Runnable task; + while ((task=_engine.getDelegatedTask())!=null) + { + task.run(); + } + break; + } + + case NEED_WRAP: + { + checkRenegotiate(); + synchronized(out_buffer) + { + try + { + _outNIOBuffer.compact(); + int put=_outNIOBuffer.putIndex(); + out_buffer.position(); + _result=null; + _result=_engine.wrap(__NO_BUFFERS,out_buffer); + if (_debug) __log.debug(_session+" flush wrap "+_result); + switch(_result.getStatus()) + { + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + Log.warn("unwrap {}",_result); + case CLOSED: + _closing=true; + } + _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); + } + finally + { + out_buffer.position(0); + } + } + + flush(); + if (isBufferingOutput()) + break loop; + + break; + } + } + } + + freeOutBuffer(); + return consumed; + } + + /* ------------------------------------------------------------ */ + @Override + public void flush() throws IOException + { + if (_outNIOBuffer==null) + return; + + int len=_outNIOBuffer.length(); + if (isBufferingOutput()) + { + int flushed=super.flush(_outNIOBuffer); + if (_debug) __log.debug(_session+" Flushed "+flushed+"/"+len); + if (isBufferingOutput()) + { + // Try again after yield.... cheaper than a reschedule. + Thread.yield(); + flushed=super.flush(_outNIOBuffer); + if (_debug) __log.debug(_session+" flushed "+flushed+"/"+len); + } + } + } + + /* ------------------------------------------------------------ */ + private void checkRenegotiate() throws IOException + { + if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen()) + { + Log.warn("SSL renegotiate denied: "+_channel); + close(); + } + } + + /* ------------------------------------------------------------ */ + private ByteBuffer extractInputBuffer(Buffer buffer) + { + assert buffer instanceof NIOBuffer; + NIOBuffer nbuf=(NIOBuffer)buffer; + ByteBuffer bbuf=nbuf.getByteBuffer(); + bbuf.position(buffer.putIndex()); + return bbuf; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if progress is made + */ + private boolean unwrap(ByteBuffer buffer) throws IOException + { + needInBuffer(); + ByteBuffer in_buffer=_inNIOBuffer.getByteBuffer(); + + if (_inNIOBuffer.hasContent()) + _inNIOBuffer.compact(); + else + _inNIOBuffer.clear(); + + int total_filled=0; + + // loop filling as much encrypted data as we can into the buffer + while (_inNIOBuffer.space()>0 && super.isOpen()) + { + try + { + int filled=super.fill(_inNIOBuffer); + if (_debug) __log.debug(_session+" unwrap filled "+filled); + // break the loop if no progress is made (we have read everything + // there is to read). + if (filled<=0) + break; + total_filled+=filled; + } + catch(IOException e) + { + if (_inNIOBuffer.length()==0) + { + if (_outNIOBuffer!=null) + { + _outNIOBuffer.clear(); + freeOutBuffer(); + } + throw e; + } + break; + } + } + + // If we have no progress and no data + if (total_filled==0 && _inNIOBuffer.length()==0) + { + if(!isOpen()) + { + if (_outNIOBuffer!=null) + _outNIOBuffer.clear(); + throw new EofException(); + } + return false; + } + + // We have some in data, so try to unwrap it. + try + { + // inBuffer is the NIO buffer inside the _inNIOBuffer, + // so update its position and limit from the inNIOBuffer. + in_buffer.position(_inNIOBuffer.getIndex()); + in_buffer.limit(_inNIOBuffer.putIndex()); + _result=null; + + // Do the unwrap + _result=_engine.unwrap(in_buffer,buffer); + if (_debug) __log.debug(_session+" unwrap unwrap "+_result); + + // skip the bytes consumed + _inNIOBuffer.skip(_result.bytesConsumed()); + } + finally + { + // reset the buffer so it can be managed by the _inNIOBuffer again. + in_buffer.position(0); + in_buffer.limit(in_buffer.capacity()); + freeInBuffer(); + } + + // handle the unwrap results + switch(_result.getStatus()) + { + case BUFFER_OVERFLOW: + throw new IllegalStateException(_result.toString()+" "+buffer.position()+" "+buffer.limit()); + + case BUFFER_UNDERFLOW: + // Not enough data, + // If we are closed, we will never get more, so EOF + // else return and we will be tried again + // later when more data arriving causes another dispatch. + if (Log.isDebugEnabled()) Log.debug("unwrap {}",_result); + if(!isOpen()) + { + _inNIOBuffer.clear(); + if (_outNIOBuffer!=null) + _outNIOBuffer.clear(); + throw new EofException(); + } + return (total_filled > 0); + + case CLOSED: + _closing=true; + // return true is some bytes somewhere were moved about. + return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; + case OK: + // return true is some bytes somewhere were moved about. + return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; + default: + Log.warn("unwrap "+_result); + throw new IOException(_result.toString()); + } + } + + + /* ------------------------------------------------------------ */ + private ByteBuffer extractOutputBuffer(Buffer buffer) + { + if (buffer.buffer() instanceof NIOBuffer) + return ((NIOBuffer)buffer.buffer()).getByteBuffer(); + + return ByteBuffer.wrap(buffer.array()); + } + + /* ------------------------------------------------------------ */ + private int wrap(final Buffer header, final Buffer buffer) throws IOException + { + _gather[0]=extractOutputBuffer(header); + + synchronized(_gather[0]) + { + _gather[0].position(header.getIndex()); + _gather[0].limit(header.putIndex()); + + _gather[1]=extractOutputBuffer(buffer); + + synchronized(_gather[1]) + { + _gather[1].position(buffer.getIndex()); + _gather[1].limit(buffer.putIndex()); + + needOutBuffer(); + ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); + synchronized(out_buffer) + { + int consumed=0; + try + { + _outNIOBuffer.clear(); + out_buffer.position(0); + out_buffer.limit(out_buffer.capacity()); + + _result=null; + _result=_engine.wrap(_gather,out_buffer); + if (_debug) __log.debug(_session+" wrap wrap "+_result); + _outNIOBuffer.setGetIndex(0); + _outNIOBuffer.setPutIndex(_result.bytesProduced()); + consumed=_result.bytesConsumed(); + } + finally + { + out_buffer.position(0); + + if (consumed>0) + { + int len=consumed<header.length()?consumed:header.length(); + header.skip(len); + consumed-=len; + _gather[0].position(0); + _gather[0].limit(_gather[0].capacity()); + } + if (consumed>0) + { + int len=consumed<buffer.length()?consumed:buffer.length(); + buffer.skip(len); + consumed-=len; + _gather[1].position(0); + _gather[1].limit(_gather[1].capacity()); + } + assert consumed==0; + + freeOutBuffer(); + } + } + } + } + + + switch(_result.getStatus()) + { + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + Log.warn("unwrap {}",_result); + + case OK: + return _result.bytesConsumed(); + case CLOSED: + _closing=true; + return _result.bytesConsumed()>0?_result.bytesConsumed():-1; + + default: + Log.warn("wrap "+_result); + throw new IOException(_result.toString()); + } + } + + /* ------------------------------------------------------------ */ + private int wrap(final Buffer buffer) throws IOException + { + _gather[0]=extractOutputBuffer(buffer); + synchronized(_gather[0]) + { + ByteBuffer bb; + + _gather[0].position(buffer.getIndex()); + _gather[0].limit(buffer.putIndex()); + + int consumed=0; + needOutBuffer(); + ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); + synchronized(out_buffer) + { + try + { + _outNIOBuffer.clear(); + out_buffer.position(0); + out_buffer.limit(out_buffer.capacity()); + _result=null; + _result=_engine.wrap(_gather[0],out_buffer); + if (_debug) __log.debug(_session+" wrap wrap "+_result); + _outNIOBuffer.setGetIndex(0); + _outNIOBuffer.setPutIndex(_result.bytesProduced()); + consumed=_result.bytesConsumed(); + } + finally + { + out_buffer.position(0); + + if (consumed>0) + { + int len=consumed<buffer.length()?consumed:buffer.length(); + buffer.skip(len); + consumed-=len; + _gather[0].position(0); + _gather[0].limit(_gather[0].capacity()); + } + assert consumed==0; + + freeOutBuffer(); + } + } + } + switch(_result.getStatus()) + { + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + Log.warn("unwrap {}",_result); + + case OK: + return _result.bytesConsumed(); + case CLOSED: + _closing=true; + return _result.bytesConsumed()>0?_result.bytesConsumed():-1; + + default: + Log.warn("wrap "+_result); + throw new IOException(_result.toString()); + } + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isBufferingInput() + { + final Buffer in = _inNIOBuffer; + return in==null?false:_inNIOBuffer.hasContent(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isBufferingOutput() + { + final NIOBuffer b=_outNIOBuffer; + return b==null?false:b.hasContent(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isBufferred() + { + return true; + } + + /* ------------------------------------------------------------ */ + public SSLEngine getSSLEngine() + { + return _engine; + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + final NIOBuffer i=_inNIOBuffer; + final NIOBuffer o=_outNIOBuffer; + return super.toString()+","+_engine.getHandshakeStatus()+", in/out="+ + (i==null?0:_inNIOBuffer.length())+"/"+(o==null?0:o.length())+" "+_result; + } +} |