| /* |
| * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.net.httpserver; |
| |
| import java.net.*; |
| import java.nio.*; |
| import java.io.*; |
| import java.nio.channels.*; |
| import java.util.*; |
| import java.util.concurrent.*; |
| import java.util.concurrent.locks.*; |
| import javax.net.ssl.*; |
| import javax.net.ssl.SSLEngineResult.*; |
| import com.sun.net.httpserver.*; |
| import com.sun.net.httpserver.spi.*; |
| |
| /** |
| * given a non-blocking SocketChannel, it produces |
| * (blocking) streams which encrypt/decrypt the SSL content |
| * and handle the SSL handshaking automatically. |
| */ |
| |
| class SSLStreams { |
| |
| SSLContext sslctx; |
| SocketChannel chan; |
| TimeSource time; |
| ServerImpl server; |
| SSLEngine engine; |
| EngineWrapper wrapper; |
| OutputStream os; |
| InputStream is; |
| static long readTimeout = ServerConfig.getReadTimeout(); |
| static long writeTimeout = ServerConfig.getWriteTimeout(); |
| |
| /* held by thread doing the hand-shake on this connection */ |
| Lock handshaking = new ReentrantLock(); |
| |
| SSLStreams (ServerImpl server, SSLContext sslctx, SocketChannel chan) throws IOException { |
| this.server = server; |
| this.time= (TimeSource)server; |
| this.sslctx= sslctx; |
| this.chan= chan; |
| InetSocketAddress addr = |
| (InetSocketAddress)chan.socket().getRemoteSocketAddress(); |
| engine = sslctx.createSSLEngine (addr.getHostName(), addr.getPort()); |
| engine.setUseClientMode (false); |
| HttpsConfigurator cfg = server.getHttpsConfigurator(); |
| configureEngine (cfg, addr); |
| wrapper = new EngineWrapper (chan, engine); |
| } |
| |
| private void configureEngine(HttpsConfigurator cfg, InetSocketAddress addr){ |
| if (cfg != null) { |
| Parameters params = new Parameters (cfg, addr); |
| cfg.configure (params); |
| SSLParameters sslParams = params.getSSLParameters(); |
| if (sslParams != null) { |
| engine.setSSLParameters (sslParams); |
| } else { |
| /* tiger compatibility */ |
| if (params.getCipherSuites() != null) { |
| try { |
| engine.setEnabledCipherSuites ( |
| params.getCipherSuites() |
| ); |
| } catch (IllegalArgumentException e) { /* LOG */} |
| } |
| engine.setNeedClientAuth (params.getNeedClientAuth()); |
| engine.setWantClientAuth (params.getWantClientAuth()); |
| if (params.getProtocols() != null) { |
| try { |
| engine.setEnabledProtocols ( |
| params.getProtocols() |
| ); |
| } catch (IllegalArgumentException e) { /* LOG */} |
| } |
| } |
| } |
| } |
| |
| class Parameters extends HttpsParameters { |
| InetSocketAddress addr; |
| SSLParameters params; |
| HttpsConfigurator cfg; |
| |
| Parameters (HttpsConfigurator cfg, InetSocketAddress addr) { |
| this.addr = addr; |
| this.cfg = cfg; |
| } |
| public InetSocketAddress getClientAddress () { |
| return addr; |
| } |
| public HttpsConfigurator getHttpsConfigurator() { |
| return cfg; |
| } |
| public void setSSLParameters (SSLParameters p) { |
| params = p; |
| } |
| SSLParameters getSSLParameters () { |
| return params; |
| } |
| } |
| |
| /** |
| * cleanup resources allocated inside this object |
| */ |
| void close () throws IOException { |
| wrapper.close(); |
| } |
| |
| /** |
| * return the SSL InputStream |
| */ |
| InputStream getInputStream () throws IOException { |
| if (is == null) { |
| is = new InputStream(); |
| } |
| return is; |
| } |
| |
| /** |
| * return the SSL OutputStream |
| */ |
| OutputStream getOutputStream () throws IOException { |
| if (os == null) { |
| os = new OutputStream(); |
| } |
| return os; |
| } |
| |
| SSLEngine getSSLEngine () { |
| return engine; |
| } |
| |
| /** |
| * request the engine to repeat the handshake on this session |
| * the handshake must be driven by reads/writes on the streams |
| * Normally, not necessary to call this. |
| */ |
| void beginHandshake() throws SSLException { |
| engine.beginHandshake(); |
| } |
| |
| class WrapperResult { |
| SSLEngineResult result; |
| |
| /* if passed in buffer was not big enough then the |
| * a reallocated buffer is returned here |
| */ |
| ByteBuffer buf; |
| } |
| |
| int app_buf_size; |
| int packet_buf_size; |
| |
| enum BufType { |
| PACKET, APPLICATION |
| }; |
| |
| private ByteBuffer allocate (BufType type) { |
| return allocate (type, -1); |
| } |
| |
| private ByteBuffer allocate (BufType type, int len) { |
| assert engine != null; |
| synchronized (this) { |
| int size; |
| if (type == BufType.PACKET) { |
| if (packet_buf_size == 0) { |
| SSLSession sess = engine.getSession(); |
| packet_buf_size = sess.getPacketBufferSize(); |
| } |
| if (len > packet_buf_size) { |
| packet_buf_size = len; |
| } |
| size = packet_buf_size; |
| } else { |
| if (app_buf_size == 0) { |
| SSLSession sess = engine.getSession(); |
| app_buf_size = sess.getApplicationBufferSize(); |
| } |
| if (len > app_buf_size) { |
| app_buf_size = len; |
| } |
| size = app_buf_size; |
| } |
| return ByteBuffer.allocate (size); |
| } |
| } |
| |
| /* reallocates the buffer by :- |
| * 1. creating a new buffer double the size of the old one |
| * 2. putting the contents of the old buffer into the new one |
| * 3. set xx_buf_size to the new size if it was smaller than new size |
| * |
| * flip is set to true if the old buffer needs to be flipped |
| * before it is copied. |
| */ |
| private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) { |
| synchronized (this) { |
| int nsize = 2 * b.capacity(); |
| ByteBuffer n = allocate (type, nsize); |
| if (flip) { |
| b.flip(); |
| } |
| n.put(b); |
| b = n; |
| } |
| return b; |
| } |
| /** |
| * This is a thin wrapper over SSLEngine and the SocketChannel, |
| * which guarantees the ordering of wraps/unwraps with respect to the underlying |
| * channel read/writes. It handles the UNDER/OVERFLOW status codes |
| * It does not handle the handshaking status codes, or the CLOSED status code |
| * though once the engine is closed, any attempt to read/write to it |
| * will get an exception. The overall result is returned. |
| * It functions synchronously/blocking |
| */ |
| class EngineWrapper { |
| |
| SocketChannel chan; |
| SSLEngine engine; |
| SelectorCache sc; |
| Selector write_selector, read_selector; |
| SelectionKey wkey, rkey; |
| Object wrapLock, unwrapLock; |
| ByteBuffer unwrap_src, wrap_dst; |
| boolean closed = false; |
| int u_remaining; // the number of bytes left in unwrap_src after an unwrap() |
| |
| EngineWrapper (SocketChannel chan, SSLEngine engine) throws IOException { |
| this.chan = chan; |
| this.engine = engine; |
| wrapLock = new Object(); |
| unwrapLock = new Object(); |
| unwrap_src = allocate(BufType.PACKET); |
| wrap_dst = allocate(BufType.PACKET); |
| sc = SelectorCache.getSelectorCache(); |
| write_selector = sc.getSelector(); |
| wkey = chan.register (write_selector, SelectionKey.OP_WRITE); |
| read_selector = sc.getSelector(); |
| wkey = chan.register (read_selector, SelectionKey.OP_READ); |
| } |
| |
| void close () throws IOException { |
| sc.freeSelector (write_selector); |
| sc.freeSelector (read_selector); |
| } |
| |
| /* try to wrap and send the data in src. Handles OVERFLOW. |
| * Might block if there is an outbound blockage or if another |
| * thread is calling wrap(). Also, might not send any data |
| * if an unwrap is needed. |
| */ |
| WrapperResult wrapAndSend(ByteBuffer src) throws IOException { |
| return wrapAndSendX(src, false); |
| } |
| |
| WrapperResult wrapAndSendX(ByteBuffer src, boolean ignoreClose) throws IOException { |
| if (closed && !ignoreClose) { |
| throw new IOException ("Engine is closed"); |
| } |
| Status status; |
| WrapperResult r = new WrapperResult(); |
| synchronized (wrapLock) { |
| wrap_dst.clear(); |
| do { |
| r.result = engine.wrap (src, wrap_dst); |
| status = r.result.getStatus(); |
| if (status == Status.BUFFER_OVERFLOW) { |
| wrap_dst = realloc (wrap_dst, true, BufType.PACKET); |
| } |
| } while (status == Status.BUFFER_OVERFLOW); |
| if (status == Status.CLOSED && !ignoreClose) { |
| closed = true; |
| return r; |
| } |
| if (r.result.bytesProduced() > 0) { |
| wrap_dst.flip(); |
| int l = wrap_dst.remaining(); |
| assert l == r.result.bytesProduced(); |
| long currtime = time.getTime(); |
| long maxtime = currtime + writeTimeout; |
| while (l>0) { |
| write_selector.select(writeTimeout); // timeout |
| currtime = time.getTime(); |
| if (currtime > maxtime) { |
| throw new SocketTimeoutException ("write timed out"); |
| } |
| write_selector.selectedKeys().clear(); |
| l -= chan.write (wrap_dst); |
| } |
| } |
| } |
| return r; |
| } |
| |
| /* block until a complete message is available and return it |
| * in dst, together with the Result. dst may have been re-allocated |
| * so caller should check the returned value in Result |
| * If handshaking is in progress then, possibly no data is returned |
| */ |
| WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException { |
| Status status = Status.OK; |
| WrapperResult r = new WrapperResult(); |
| r.buf = dst; |
| if (closed) { |
| throw new IOException ("Engine is closed"); |
| } |
| boolean needData; |
| if (u_remaining > 0) { |
| unwrap_src.compact(); |
| unwrap_src.flip(); |
| needData = false; |
| } else { |
| unwrap_src.clear(); |
| needData = true; |
| } |
| synchronized (unwrapLock) { |
| int x,y; |
| do { |
| if (needData) { |
| long currTime = time.getTime(); |
| long maxtime = currTime + readTimeout; |
| do { |
| if (currTime > maxtime) { |
| throw new SocketTimeoutException ("read timedout"); |
| } |
| y = read_selector.select (readTimeout); |
| currTime = time.getTime(); |
| } while (y != 1); |
| read_selector.selectedKeys().clear(); |
| x = chan.read (unwrap_src); |
| if (x == -1) { |
| throw new IOException ("connection closed for reading"); |
| } |
| unwrap_src.flip(); |
| } |
| r.result = engine.unwrap (unwrap_src, r.buf); |
| status = r.result.getStatus(); |
| if (status == Status.BUFFER_UNDERFLOW) { |
| if (unwrap_src.limit() == unwrap_src.capacity()) { |
| /* buffer not big enough */ |
| unwrap_src = realloc ( |
| unwrap_src, false, BufType.PACKET |
| ); |
| } else { |
| /* Buffer not full, just need to read more |
| * data off the channel. Reset pointers |
| * for reading off SocketChannel |
| */ |
| unwrap_src.position (unwrap_src.limit()); |
| unwrap_src.limit (unwrap_src.capacity()); |
| } |
| needData = true; |
| } else if (status == Status.BUFFER_OVERFLOW) { |
| r.buf = realloc (r.buf, true, BufType.APPLICATION); |
| needData = false; |
| } else if (status == Status.CLOSED) { |
| closed = true; |
| r.buf.flip(); |
| return r; |
| } |
| } while (status != Status.OK); |
| } |
| u_remaining = unwrap_src.remaining(); |
| return r; |
| } |
| } |
| |
| /** |
| * send the data in the given ByteBuffer. If a handshake is needed |
| * then this is handled within this method. When this call returns, |
| * all of the given user data has been sent and any handshake has been |
| * completed. Caller should check if engine has been closed. |
| */ |
| public WrapperResult sendData (ByteBuffer src) throws IOException { |
| WrapperResult r=null; |
| while (src.remaining() > 0) { |
| r = wrapper.wrapAndSend(src); |
| Status status = r.result.getStatus(); |
| if (status == Status.CLOSED) { |
| doClosure (); |
| return r; |
| } |
| HandshakeStatus hs_status = r.result.getHandshakeStatus(); |
| if (hs_status != HandshakeStatus.FINISHED && |
| hs_status != HandshakeStatus.NOT_HANDSHAKING) |
| { |
| doHandshake(hs_status); |
| } |
| } |
| return r; |
| } |
| |
| /** |
| * read data thru the engine into the given ByteBuffer. If the |
| * given buffer was not large enough, a new one is allocated |
| * and returned. This call handles handshaking automatically. |
| * Caller should check if engine has been closed. |
| */ |
| public WrapperResult recvData (ByteBuffer dst) throws IOException { |
| /* we wait until some user data arrives */ |
| WrapperResult r = null; |
| assert dst.position() == 0; |
| while (dst.position() == 0) { |
| r = wrapper.recvAndUnwrap (dst); |
| dst = (r.buf != dst) ? r.buf: dst; |
| Status status = r.result.getStatus(); |
| if (status == Status.CLOSED) { |
| doClosure (); |
| return r; |
| } |
| |
| HandshakeStatus hs_status = r.result.getHandshakeStatus(); |
| if (hs_status != HandshakeStatus.FINISHED && |
| hs_status != HandshakeStatus.NOT_HANDSHAKING) |
| { |
| doHandshake (hs_status); |
| } |
| } |
| dst.flip(); |
| return r; |
| } |
| |
| /* we've received a close notify. Need to call wrap to send |
| * the response |
| */ |
| void doClosure () throws IOException { |
| try { |
| handshaking.lock(); |
| ByteBuffer tmp = allocate(BufType.APPLICATION); |
| WrapperResult r; |
| do { |
| tmp.clear(); |
| tmp.flip (); |
| r = wrapper.wrapAndSendX (tmp, true); |
| } while (r.result.getStatus() != Status.CLOSED); |
| } finally { |
| handshaking.unlock(); |
| } |
| } |
| |
| /* do the (complete) handshake after acquiring the handshake lock. |
| * If two threads call this at the same time, then we depend |
| * on the wrapper methods being idempotent. eg. if wrapAndSend() |
| * is called with no data to send then there must be no problem |
| */ |
| void doHandshake (HandshakeStatus hs_status) throws IOException { |
| try { |
| handshaking.lock(); |
| ByteBuffer tmp = allocate(BufType.APPLICATION); |
| while (hs_status != HandshakeStatus.FINISHED && |
| hs_status != HandshakeStatus.NOT_HANDSHAKING) |
| { |
| WrapperResult r = null; |
| switch (hs_status) { |
| case NEED_TASK: |
| Runnable task; |
| while ((task = engine.getDelegatedTask()) != null) { |
| /* run in current thread, because we are already |
| * running an external Executor |
| */ |
| task.run(); |
| } |
| /* fall thru - call wrap again */ |
| case NEED_WRAP: |
| tmp.clear(); |
| tmp.flip(); |
| r = wrapper.wrapAndSend(tmp); |
| break; |
| |
| case NEED_UNWRAP: |
| tmp.clear(); |
| r = wrapper.recvAndUnwrap (tmp); |
| if (r.buf != tmp) { |
| tmp = r.buf; |
| } |
| assert tmp.position() == 0; |
| break; |
| } |
| hs_status = r.result.getHandshakeStatus(); |
| } |
| } finally { |
| handshaking.unlock(); |
| } |
| } |
| |
| /** |
| * represents an SSL input stream. Multiple https requests can |
| * be sent over one stream. closing this stream causes an SSL close |
| * input. |
| */ |
| class InputStream extends java.io.InputStream { |
| |
| ByteBuffer bbuf; |
| boolean closed = false; |
| |
| /* this stream eof */ |
| boolean eof = false; |
| |
| boolean needData = true; |
| |
| InputStream () { |
| bbuf = allocate (BufType.APPLICATION); |
| } |
| |
| public int read (byte[] buf, int off, int len) throws IOException { |
| if (closed) { |
| throw new IOException ("SSL stream is closed"); |
| } |
| if (eof) { |
| return 0; |
| } |
| int available=0; |
| if (!needData) { |
| available = bbuf.remaining(); |
| needData = (available==0); |
| } |
| if (needData) { |
| bbuf.clear(); |
| WrapperResult r = recvData (bbuf); |
| bbuf = r.buf== bbuf? bbuf: r.buf; |
| if ((available=bbuf.remaining()) == 0) { |
| eof = true; |
| return 0; |
| } else { |
| needData = false; |
| } |
| } |
| /* copy as much as possible from buf into users buf */ |
| if (len > available) { |
| len = available; |
| } |
| bbuf.get (buf, off, len); |
| return len; |
| } |
| |
| public int available () throws IOException { |
| return bbuf.remaining(); |
| } |
| |
| public boolean markSupported () { |
| return false; /* not possible with SSLEngine */ |
| } |
| |
| public void reset () throws IOException { |
| throw new IOException ("mark/reset not supported"); |
| } |
| |
| public long skip (long s) throws IOException { |
| int n = (int)s; |
| if (closed) { |
| throw new IOException ("SSL stream is closed"); |
| } |
| if (eof) { |
| return 0; |
| } |
| int ret = n; |
| while (n > 0) { |
| if (bbuf.remaining() >= n) { |
| bbuf.position (bbuf.position()+n); |
| return ret; |
| } else { |
| n -= bbuf.remaining(); |
| bbuf.clear(); |
| WrapperResult r = recvData (bbuf); |
| bbuf = r.buf==bbuf? bbuf: r.buf; |
| } |
| } |
| return ret; /* not reached */ |
| } |
| |
| /** |
| * close the SSL connection. All data must have been consumed |
| * before this is called. Otherwise an exception will be thrown. |
| * [Note. May need to revisit this. not quite the normal close() symantics |
| */ |
| public void close () throws IOException { |
| eof = true; |
| engine.closeInbound (); |
| } |
| |
| public int read (byte[] buf) throws IOException { |
| return read (buf, 0, buf.length); |
| } |
| |
| byte single[] = new byte [1]; |
| |
| public int read () throws IOException { |
| int n = read (single, 0, 1); |
| if (n == 0) { |
| return -1; |
| } else { |
| return single[0] & 0xFF; |
| } |
| } |
| } |
| |
| /** |
| * represents an SSL output stream. plain text data written to this stream |
| * is encrypted by the stream. Multiple HTTPS responses can be sent on |
| * one stream. closing this stream initiates an SSL closure |
| */ |
| class OutputStream extends java.io.OutputStream { |
| ByteBuffer buf; |
| boolean closed = false; |
| byte single[] = new byte[1]; |
| |
| OutputStream() { |
| buf = allocate(BufType.APPLICATION); |
| } |
| |
| public void write(int b) throws IOException { |
| single[0] = (byte)b; |
| write (single, 0, 1); |
| } |
| |
| public void write(byte b[]) throws IOException { |
| write (b, 0, b.length); |
| } |
| public void write(byte b[], int off, int len) throws IOException { |
| if (closed) { |
| throw new IOException ("output stream is closed"); |
| } |
| while (len > 0) { |
| int l = len > buf.capacity() ? buf.capacity() : len; |
| buf.clear(); |
| buf.put (b, off, l); |
| len -= l; |
| off += l; |
| buf.flip(); |
| WrapperResult r = sendData (buf); |
| if (r.result.getStatus() == Status.CLOSED) { |
| closed = true; |
| if (len > 0) { |
| throw new IOException ("output stream is closed"); |
| } |
| } |
| } |
| } |
| |
| public void flush() throws IOException { |
| /* no-op */ |
| } |
| |
| public void close() throws IOException { |
| WrapperResult r=null; |
| engine.closeOutbound(); |
| closed = true; |
| HandshakeStatus stat = HandshakeStatus.NEED_WRAP; |
| buf.clear(); |
| while (stat == HandshakeStatus.NEED_WRAP) { |
| r = wrapper.wrapAndSend (buf); |
| stat = r.result.getHandshakeStatus(); |
| } |
| assert r.result.getStatus() == Status.CLOSED; |
| } |
| } |
| } |