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