Path: blob/master/src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java
41159 views
/*1* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.net.httpserver;2627import java.net.*;28import java.nio.*;29import java.io.*;30import java.nio.channels.*;31import java.util.concurrent.locks.*;32import javax.net.ssl.*;33import javax.net.ssl.SSLEngineResult.*;34import com.sun.net.httpserver.*;3536/**37* given a non-blocking SocketChannel, it produces38* (blocking) streams which encrypt/decrypt the SSL content39* and handle the SSL handshaking automatically.40*/4142class SSLStreams {4344SSLContext sslctx;45SocketChannel chan;46TimeSource time;47ServerImpl server;48SSLEngine engine;49EngineWrapper wrapper;50OutputStream os;51InputStream is;5253/* held by thread doing the hand-shake on this connection */54Lock handshaking = new ReentrantLock();5556SSLStreams (ServerImpl server, SSLContext sslctx, SocketChannel chan) throws IOException {57this.server = server;58this.time= (TimeSource)server;59this.sslctx= sslctx;60this.chan= chan;61InetSocketAddress addr =62(InetSocketAddress)chan.socket().getRemoteSocketAddress();63engine = sslctx.createSSLEngine (addr.getHostName(), addr.getPort());64engine.setUseClientMode (false);65HttpsConfigurator cfg = server.getHttpsConfigurator();66configureEngine (cfg, addr);67wrapper = new EngineWrapper (chan, engine);68}6970private void configureEngine(HttpsConfigurator cfg, InetSocketAddress addr){71if (cfg != null) {72Parameters params = new Parameters (cfg, addr);73//BEGIN_TIGER_EXCLUDE74cfg.configure (params);75SSLParameters sslParams = params.getSSLParameters();76if (sslParams != null) {77engine.setSSLParameters (sslParams);78} else79//END_TIGER_EXCLUDE80{81/* tiger compatibility */82if (params.getCipherSuites() != null) {83try {84engine.setEnabledCipherSuites (85params.getCipherSuites()86);87} catch (IllegalArgumentException e) { /* LOG */}88}89engine.setNeedClientAuth (params.getNeedClientAuth());90engine.setWantClientAuth (params.getWantClientAuth());91if (params.getProtocols() != null) {92try {93engine.setEnabledProtocols (94params.getProtocols()95);96} catch (IllegalArgumentException e) { /* LOG */}97}98}99}100}101102class Parameters extends HttpsParameters {103InetSocketAddress addr;104HttpsConfigurator cfg;105106Parameters (HttpsConfigurator cfg, InetSocketAddress addr) {107this.addr = addr;108this.cfg = cfg;109}110public InetSocketAddress getClientAddress () {111return addr;112}113public HttpsConfigurator getHttpsConfigurator() {114return cfg;115}116//BEGIN_TIGER_EXCLUDE117SSLParameters params;118public void setSSLParameters (SSLParameters p) {119params = p;120}121SSLParameters getSSLParameters () {122return params;123}124//END_TIGER_EXCLUDE125}126127/**128* cleanup resources allocated inside this object129*/130void close () throws IOException {131wrapper.close();132}133134/**135* return the SSL InputStream136*/137InputStream getInputStream () throws IOException {138if (is == null) {139is = new InputStream();140}141return is;142}143144/**145* return the SSL OutputStream146*/147OutputStream getOutputStream () throws IOException {148if (os == null) {149os = new OutputStream();150}151return os;152}153154SSLEngine getSSLEngine () {155return engine;156}157158/**159* request the engine to repeat the handshake on this session160* the handshake must be driven by reads/writes on the streams161* Normally, not necessary to call this.162*/163void beginHandshake() throws SSLException {164engine.beginHandshake();165}166167class WrapperResult {168SSLEngineResult result;169170/* if passed in buffer was not big enough then the171* a reallocated buffer is returned here172*/173ByteBuffer buf;174}175176int app_buf_size;177int packet_buf_size;178179enum BufType {180PACKET, APPLICATION181};182183private ByteBuffer allocate (BufType type) {184return allocate (type, -1);185}186187private ByteBuffer allocate (BufType type, int len) {188assert engine != null;189synchronized (this) {190int size;191if (type == BufType.PACKET) {192if (packet_buf_size == 0) {193SSLSession sess = engine.getSession();194packet_buf_size = sess.getPacketBufferSize();195}196if (len > packet_buf_size) {197packet_buf_size = len;198}199size = packet_buf_size;200} else {201if (app_buf_size == 0) {202SSLSession sess = engine.getSession();203app_buf_size = sess.getApplicationBufferSize();204}205if (len > app_buf_size) {206app_buf_size = len;207}208size = app_buf_size;209}210return ByteBuffer.allocate (size);211}212}213214/* reallocates the buffer by :-215* 1. creating a new buffer double the size of the old one216* 2. putting the contents of the old buffer into the new one217* 3. set xx_buf_size to the new size if it was smaller than new size218*219* flip is set to true if the old buffer needs to be flipped220* before it is copied.221*/222private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) {223synchronized (this) {224int nsize = 2 * b.capacity();225ByteBuffer n = allocate (type, nsize);226if (flip) {227b.flip();228}229n.put(b);230b = n;231}232return b;233}234/**235* This is a thin wrapper over SSLEngine and the SocketChannel,236* which guarantees the ordering of wraps/unwraps with respect to the underlying237* channel read/writes. It handles the UNDER/OVERFLOW status codes238* It does not handle the handshaking status codes, or the CLOSED status code239* though once the engine is closed, any attempt to read/write to it240* will get an exception. The overall result is returned.241* It functions synchronously/blocking242*/243class EngineWrapper {244245SocketChannel chan;246SSLEngine engine;247Object wrapLock, unwrapLock;248ByteBuffer unwrap_src, wrap_dst;249boolean closed = false;250int u_remaining; // the number of bytes left in unwrap_src after an unwrap()251252EngineWrapper (SocketChannel chan, SSLEngine engine) throws IOException {253this.chan = chan;254this.engine = engine;255wrapLock = new Object();256unwrapLock = new Object();257unwrap_src = allocate(BufType.PACKET);258wrap_dst = allocate(BufType.PACKET);259}260261void close () throws IOException {262}263264/* try to wrap and send the data in src. Handles OVERFLOW.265* Might block if there is an outbound blockage or if another266* thread is calling wrap(). Also, might not send any data267* if an unwrap is needed.268*/269WrapperResult wrapAndSend(ByteBuffer src) throws IOException {270return wrapAndSendX(src, false);271}272273WrapperResult wrapAndSendX(ByteBuffer src, boolean ignoreClose) throws IOException {274if (closed && !ignoreClose) {275throw new IOException ("Engine is closed");276}277Status status;278WrapperResult r = new WrapperResult();279synchronized (wrapLock) {280wrap_dst.clear();281do {282r.result = engine.wrap (src, wrap_dst);283status = r.result.getStatus();284if (status == Status.BUFFER_OVERFLOW) {285wrap_dst = realloc (wrap_dst, true, BufType.PACKET);286}287} while (status == Status.BUFFER_OVERFLOW);288if (status == Status.CLOSED && !ignoreClose) {289closed = true;290return r;291}292if (r.result.bytesProduced() > 0) {293wrap_dst.flip();294int l = wrap_dst.remaining();295assert l == r.result.bytesProduced();296while (l>0) {297l -= chan.write (wrap_dst);298}299}300}301return r;302}303304/* block until a complete message is available and return it305* in dst, together with the Result. dst may have been re-allocated306* so caller should check the returned value in Result307* If handshaking is in progress then, possibly no data is returned308*/309WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException {310Status status = Status.OK;311WrapperResult r = new WrapperResult();312r.buf = dst;313if (closed) {314throw new IOException ("Engine is closed");315}316boolean needData;317if (u_remaining > 0) {318unwrap_src.compact();319unwrap_src.flip();320needData = false;321} else {322unwrap_src.clear();323needData = true;324}325synchronized (unwrapLock) {326int x;327do {328if (needData) {329do {330x = chan.read (unwrap_src);331} while (x == 0);332if (x == -1) {333throw new IOException ("connection closed for reading");334}335unwrap_src.flip();336}337r.result = engine.unwrap (unwrap_src, r.buf);338status = r.result.getStatus();339if (status == Status.BUFFER_UNDERFLOW) {340if (unwrap_src.limit() == unwrap_src.capacity()) {341/* buffer not big enough */342unwrap_src = realloc (343unwrap_src, false, BufType.PACKET344);345} else {346/* Buffer not full, just need to read more347* data off the channel. Reset pointers348* for reading off SocketChannel349*/350unwrap_src.position (unwrap_src.limit());351unwrap_src.limit (unwrap_src.capacity());352}353needData = true;354} else if (status == Status.BUFFER_OVERFLOW) {355r.buf = realloc (r.buf, true, BufType.APPLICATION);356needData = false;357} else if (status == Status.CLOSED) {358closed = true;359r.buf.flip();360return r;361}362} while (status != Status.OK);363}364u_remaining = unwrap_src.remaining();365return r;366}367}368369/**370* send the data in the given ByteBuffer. If a handshake is needed371* then this is handled within this method. When this call returns,372* all of the given user data has been sent and any handshake has been373* completed. Caller should check if engine has been closed.374*/375public WrapperResult sendData (ByteBuffer src) throws IOException {376WrapperResult r=null;377while (src.remaining() > 0) {378r = wrapper.wrapAndSend(src);379Status status = r.result.getStatus();380if (status == Status.CLOSED) {381doClosure ();382return r;383}384HandshakeStatus hs_status = r.result.getHandshakeStatus();385if (hs_status != HandshakeStatus.FINISHED &&386hs_status != HandshakeStatus.NOT_HANDSHAKING)387{388doHandshake(hs_status);389}390}391return r;392}393394/**395* read data thru the engine into the given ByteBuffer. If the396* given buffer was not large enough, a new one is allocated397* and returned. This call handles handshaking automatically.398* Caller should check if engine has been closed.399*/400public WrapperResult recvData (ByteBuffer dst) throws IOException {401/* we wait until some user data arrives */402WrapperResult r = null;403assert dst.position() == 0;404while (dst.position() == 0) {405r = wrapper.recvAndUnwrap (dst);406dst = (r.buf != dst) ? r.buf: dst;407Status status = r.result.getStatus();408if (status == Status.CLOSED) {409doClosure ();410return r;411}412413HandshakeStatus hs_status = r.result.getHandshakeStatus();414if (hs_status != HandshakeStatus.FINISHED &&415hs_status != HandshakeStatus.NOT_HANDSHAKING)416{417doHandshake (hs_status);418}419}420dst.flip();421return r;422}423424/* we've received a close notify. Need to call wrap to send425* the response426*/427void doClosure () throws IOException {428try {429handshaking.lock();430ByteBuffer tmp = allocate(BufType.APPLICATION);431WrapperResult r;432Status st;433HandshakeStatus hs;434do {435tmp.clear();436tmp.flip ();437r = wrapper.wrapAndSendX (tmp, true);438hs = r.result.getHandshakeStatus();439st = r.result.getStatus();440} while (st != Status.CLOSED &&441!(st == Status.OK && hs == HandshakeStatus.NOT_HANDSHAKING));442} finally {443handshaking.unlock();444}445}446447/* do the (complete) handshake after acquiring the handshake lock.448* If two threads call this at the same time, then we depend449* on the wrapper methods being idempotent. eg. if wrapAndSend()450* is called with no data to send then there must be no problem451*/452@SuppressWarnings("fallthrough")453void doHandshake (HandshakeStatus hs_status) throws IOException {454try {455handshaking.lock();456ByteBuffer tmp = allocate(BufType.APPLICATION);457while (hs_status != HandshakeStatus.FINISHED &&458hs_status != HandshakeStatus.NOT_HANDSHAKING)459{460WrapperResult r = null;461switch (hs_status) {462case NEED_TASK:463Runnable task;464while ((task = engine.getDelegatedTask()) != null) {465/* run in current thread, because we are already466* running an external Executor467*/468task.run();469}470/* fall thru - call wrap again */471case NEED_WRAP:472tmp.clear();473tmp.flip();474r = wrapper.wrapAndSend(tmp);475break;476477case NEED_UNWRAP:478tmp.clear();479r = wrapper.recvAndUnwrap (tmp);480if (r.buf != tmp) {481tmp = r.buf;482}483assert tmp.position() == 0;484break;485}486hs_status = r.result.getHandshakeStatus();487}488} finally {489handshaking.unlock();490}491}492493/**494* represents an SSL input stream. Multiple https requests can495* be sent over one stream. closing this stream causes an SSL close496* input.497*/498class InputStream extends java.io.InputStream {499500ByteBuffer bbuf;501boolean closed = false;502503/* this stream eof */504boolean eof = false;505506boolean needData = true;507508InputStream () {509bbuf = allocate (BufType.APPLICATION);510}511512public int read (byte[] buf, int off, int len) throws IOException {513if (closed) {514throw new IOException ("SSL stream is closed");515}516if (eof) {517return 0;518}519int available=0;520if (!needData) {521available = bbuf.remaining();522needData = (available==0);523}524if (needData) {525bbuf.clear();526WrapperResult r = recvData (bbuf);527bbuf = r.buf== bbuf? bbuf: r.buf;528if ((available=bbuf.remaining()) == 0) {529eof = true;530return 0;531} else {532needData = false;533}534}535/* copy as much as possible from buf into users buf */536if (len > available) {537len = available;538}539bbuf.get (buf, off, len);540return len;541}542543public int available () throws IOException {544return bbuf.remaining();545}546547public boolean markSupported () {548return false; /* not possible with SSLEngine */549}550551public void reset () throws IOException {552throw new IOException ("mark/reset not supported");553}554555public long skip (long s) throws IOException {556int n = (int)s;557if (closed) {558throw new IOException ("SSL stream is closed");559}560if (eof) {561return 0;562}563int ret = n;564while (n > 0) {565if (bbuf.remaining() >= n) {566bbuf.position (bbuf.position()+n);567return ret;568} else {569n -= bbuf.remaining();570bbuf.clear();571WrapperResult r = recvData (bbuf);572bbuf = r.buf==bbuf? bbuf: r.buf;573}574}575return ret; /* not reached */576}577578/**579* close the SSL connection. All data must have been consumed580* before this is called. Otherwise an exception will be thrown.581* [Note. May need to revisit this. not quite the normal close() symantics582*/583public void close () throws IOException {584eof = true;585engine.closeInbound ();586}587588public int read (byte[] buf) throws IOException {589return read (buf, 0, buf.length);590}591592byte single[] = new byte [1];593594public int read () throws IOException {595int n = read (single, 0, 1);596if (n == 0) {597return -1;598} else {599return single[0] & 0xFF;600}601}602}603604/**605* represents an SSL output stream. plain text data written to this stream606* is encrypted by the stream. Multiple HTTPS responses can be sent on607* one stream. closing this stream initiates an SSL closure608*/609class OutputStream extends java.io.OutputStream {610ByteBuffer buf;611boolean closed = false;612byte single[] = new byte[1];613614OutputStream() {615buf = allocate(BufType.APPLICATION);616}617618public void write(int b) throws IOException {619single[0] = (byte)b;620write (single, 0, 1);621}622623public void write(byte b[]) throws IOException {624write (b, 0, b.length);625}626public void write(byte b[], int off, int len) throws IOException {627if (closed) {628throw new IOException ("output stream is closed");629}630while (len > 0) {631int l = len > buf.capacity() ? buf.capacity() : len;632buf.clear();633buf.put (b, off, l);634len -= l;635off += l;636buf.flip();637WrapperResult r = sendData (buf);638if (r.result.getStatus() == Status.CLOSED) {639closed = true;640if (len > 0) {641throw new IOException ("output stream is closed");642}643}644}645}646647public void flush() throws IOException {648/* no-op */649}650651public void close() throws IOException {652WrapperResult r=null;653engine.closeOutbound();654closed = true;655HandshakeStatus stat = HandshakeStatus.NEED_WRAP;656buf.clear();657while (stat == HandshakeStatus.NEED_WRAP) {658r = wrapper.wrapAndSend (buf);659stat = r.result.getHandshakeStatus();660}661assert r.result.getStatus() == Status.CLOSED662: "status is: " + r.result.getStatus()663+ ", handshakeStatus is: " + stat;664}665}666}667668669