1 /*
   2  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.net.httpserver;
  27 
  28 import java.net.*;
  29 import java.nio.*;
  30 import java.io.*;
  31 import java.nio.channels.*;
  32 import java.util.*;
  33 import java.util.concurrent.*;
  34 import java.util.concurrent.locks.*;
  35 import javax.net.ssl.*;
  36 import javax.net.ssl.SSLEngineResult.*;
  37 import com.sun.net.httpserver.*;
  38 import com.sun.net.httpserver.spi.*;
  39 
  40 /**
  41  * given a non-blocking SocketChannel, it produces
  42  * (blocking) streams which encrypt/decrypt the SSL content
  43  * and handle the SSL handshaking automatically.
  44  */
  45 
  46 class SSLStreams {
  47 
  48     SSLContext sslctx;
  49     SocketChannel chan;
  50     TimeSource time;
  51     ServerImpl server;
  52     SSLEngine engine;
  53     EngineWrapper wrapper;
  54     OutputStream os;
  55     InputStream is;
  56 
  57     /* held by thread doing the hand-shake on this connection */
  58     Lock handshaking = new ReentrantLock();
  59 
  60     SSLStreams (ServerImpl server, SSLContext sslctx, SocketChannel chan) throws IOException {
  61         this.server = server;
  62         this.time= (TimeSource)server;
  63         this.sslctx= sslctx;
  64         this.chan= chan;
  65         InetSocketAddress addr =
  66                 (InetSocketAddress)chan.socket().getRemoteSocketAddress();
  67         engine = sslctx.createSSLEngine (addr.getHostName(), addr.getPort());
  68         engine.setUseClientMode (false);
  69         HttpsConfigurator cfg = server.getHttpsConfigurator();
  70         configureEngine (cfg, addr);
  71         wrapper = new EngineWrapper (chan, engine);
  72     }
  73 
  74     private void configureEngine(HttpsConfigurator cfg, InetSocketAddress addr){
  75         if (cfg != null) {
  76             Parameters params = new Parameters (cfg, addr);
  77 //BEGIN_TIGER_EXCLUDE
  78             cfg.configure (params);
  79             SSLParameters sslParams = params.getSSLParameters();
  80             if (sslParams != null) {
  81                 engine.setSSLParameters (sslParams);
  82             } else
  83 //END_TIGER_EXCLUDE
  84             {
  85                 /* tiger compatibility */
  86                 if (params.getCipherSuites() != null) {
  87                     try {
  88                         engine.setEnabledCipherSuites (
  89                             params.getCipherSuites()
  90                         );
  91                     } catch (IllegalArgumentException e) { /* LOG */}
  92                 }
  93                 engine.setNeedClientAuth (params.getNeedClientAuth());
  94                 engine.setWantClientAuth (params.getWantClientAuth());
  95                 if (params.getProtocols() != null) {
  96                     try {
  97                         engine.setEnabledProtocols (
  98                             params.getProtocols()
  99                         );
 100                     } catch (IllegalArgumentException e) { /* LOG */}
 101                 }
 102             }
 103         }
 104     }
 105 
 106     class Parameters extends HttpsParameters {
 107         InetSocketAddress addr;
 108         HttpsConfigurator cfg;
 109 
 110         Parameters (HttpsConfigurator cfg, InetSocketAddress addr) {
 111             this.addr = addr;
 112             this.cfg = cfg;
 113         }
 114         public InetSocketAddress getClientAddress () {
 115             return addr;
 116         }
 117         public HttpsConfigurator getHttpsConfigurator() {
 118             return cfg;
 119         }
 120 //BEGIN_TIGER_EXCLUDE
 121         SSLParameters params;
 122         public void setSSLParameters (SSLParameters p) {
 123             params = p;
 124         }
 125         SSLParameters getSSLParameters () {
 126             return params;
 127         }
 128 //END_TIGER_EXCLUDE
 129     }
 130 
 131     /**
 132      * cleanup resources allocated inside this object
 133      */
 134     void close () throws IOException {
 135         wrapper.close();
 136     }
 137 
 138     /**
 139      * return the SSL InputStream
 140      */
 141     InputStream getInputStream () throws IOException {
 142         if (is == null) {
 143             is = new InputStream();
 144         }
 145         return is;
 146     }
 147 
 148     /**
 149      * return the SSL OutputStream
 150      */
 151     OutputStream getOutputStream () throws IOException {
 152         if (os == null) {
 153             os = new OutputStream();
 154         }
 155         return os;
 156     }
 157 
 158     SSLEngine getSSLEngine () {
 159         return engine;
 160     }
 161 
 162     /**
 163      * request the engine to repeat the handshake on this session
 164      * the handshake must be driven by reads/writes on the streams
 165      * Normally, not necessary to call this.
 166      */
 167     void beginHandshake() throws SSLException {
 168         engine.beginHandshake();
 169     }
 170 
 171     class WrapperResult {
 172         SSLEngineResult result;
 173 
 174         /* if passed in buffer was not big enough then the
 175          * a reallocated buffer is returned here
 176          */
 177         ByteBuffer buf;
 178     }
 179 
 180     int app_buf_size;
 181     int packet_buf_size;
 182 
 183     enum BufType {
 184         PACKET, APPLICATION
 185     };
 186 
 187     private ByteBuffer allocate (BufType type) {
 188         return allocate (type, -1);
 189     }
 190 
 191     private ByteBuffer allocate (BufType type, int len) {
 192         assert engine != null;
 193         synchronized (this) {
 194             int size;
 195             if (type == BufType.PACKET) {
 196                 if (packet_buf_size == 0) {
 197                     SSLSession sess = engine.getSession();
 198                     packet_buf_size = sess.getPacketBufferSize();
 199                 }
 200                 if (len > packet_buf_size) {
 201                     packet_buf_size = len;
 202                 }
 203                 size = packet_buf_size;
 204             } else {
 205                 if (app_buf_size == 0) {
 206                     SSLSession sess = engine.getSession();
 207                     app_buf_size = sess.getApplicationBufferSize();
 208                 }
 209                 if (len > app_buf_size) {
 210                     app_buf_size = len;
 211                 }
 212                 size = app_buf_size;
 213             }
 214             return ByteBuffer.allocate (size);
 215         }
 216     }
 217 
 218     /* reallocates the buffer by :-
 219      * 1. creating a new buffer double the size of the old one
 220      * 2. putting the contents of the old buffer into the new one
 221      * 3. set xx_buf_size to the new size if it was smaller than new size
 222      *
 223      * flip is set to true if the old buffer needs to be flipped
 224      * before it is copied.
 225      */
 226     private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) {
 227         synchronized (this) {
 228             int nsize = 2 * b.capacity();
 229             ByteBuffer n = allocate (type, nsize);
 230             if (flip) {
 231                 b.flip();
 232             }
 233             n.put(b);
 234             b = n;
 235         }
 236         return b;
 237     }
 238     /**
 239      * This is a thin wrapper over SSLEngine and the SocketChannel,
 240      * which guarantees the ordering of wraps/unwraps with respect to the underlying
 241      * channel read/writes. It handles the UNDER/OVERFLOW status codes
 242      * It does not handle the handshaking status codes, or the CLOSED status code
 243      * though once the engine is closed, any attempt to read/write to it
 244      * will get an exception.  The overall result is returned.
 245      * It functions synchronously/blocking
 246      */
 247     class EngineWrapper {
 248 
 249         SocketChannel chan;
 250         SSLEngine engine;
 251         Object wrapLock, unwrapLock;
 252         ByteBuffer unwrap_src, wrap_dst;
 253         boolean closed = false;
 254         int u_remaining; // the number of bytes left in unwrap_src after an unwrap()
 255 
 256         EngineWrapper (SocketChannel chan, SSLEngine engine) throws IOException {
 257             this.chan = chan;
 258             this.engine = engine;
 259             wrapLock = new Object();
 260             unwrapLock = new Object();
 261             unwrap_src = allocate(BufType.PACKET);
 262             wrap_dst = allocate(BufType.PACKET);
 263         }
 264 
 265         void close () throws IOException {
 266         }
 267 
 268         /* try to wrap and send the data in src. Handles OVERFLOW.
 269          * Might block if there is an outbound blockage or if another
 270          * thread is calling wrap(). Also, might not send any data
 271          * if an unwrap is needed.
 272          */
 273         WrapperResult wrapAndSend(ByteBuffer src) throws IOException {
 274             return wrapAndSendX(src, false);
 275         }
 276 
 277         WrapperResult wrapAndSendX(ByteBuffer src, boolean ignoreClose) throws IOException {
 278             if (closed && !ignoreClose) {
 279                 throw new IOException ("Engine is closed");
 280             }
 281             Status status;
 282             WrapperResult r = new WrapperResult();
 283             synchronized (wrapLock) {
 284                 wrap_dst.clear();
 285                 do {
 286                     r.result = engine.wrap (src, wrap_dst);
 287                     status = r.result.getStatus();
 288                     if (status == Status.BUFFER_OVERFLOW) {
 289                         wrap_dst = realloc (wrap_dst, true, BufType.PACKET);
 290                     }
 291                 } while (status == Status.BUFFER_OVERFLOW);
 292                 if (status == Status.CLOSED && !ignoreClose) {
 293                     closed = true;
 294                     return r;
 295                 }
 296                 if (r.result.bytesProduced() > 0) {
 297                     wrap_dst.flip();
 298                     int l = wrap_dst.remaining();
 299                     assert l == r.result.bytesProduced();
 300                     while (l>0) {
 301                         l -= chan.write (wrap_dst);
 302                     }
 303                 }
 304             }
 305             return r;
 306         }
 307 
 308         /* block until a complete message is available and return it
 309          * in dst, together with the Result. dst may have been re-allocated
 310          * so caller should check the returned value in Result
 311          * If handshaking is in progress then, possibly no data is returned
 312          */
 313         WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException {
 314             Status status = Status.OK;
 315             WrapperResult r = new WrapperResult();
 316             r.buf = dst;
 317             if (closed) {
 318                 throw new IOException ("Engine is closed");
 319             }
 320             boolean needData;
 321             if (u_remaining > 0) {
 322                 unwrap_src.compact();
 323                 unwrap_src.flip();
 324                 needData = false;
 325             } else {
 326                 unwrap_src.clear();
 327                 needData = true;
 328             }
 329             synchronized (unwrapLock) {
 330                 int x;
 331                 do {
 332                     if (needData) {
 333                         do {
 334                         x = chan.read (unwrap_src);
 335                         } while (x == 0);
 336                         if (x == -1) {
 337                             throw new IOException ("connection closed for reading");
 338                         }
 339                         unwrap_src.flip();
 340                     }
 341                     r.result = engine.unwrap (unwrap_src, r.buf);
 342                     status = r.result.getStatus();
 343                     if (status == Status.BUFFER_UNDERFLOW) {
 344                         if (unwrap_src.limit() == unwrap_src.capacity()) {
 345                             /* buffer not big enough */
 346                             unwrap_src = realloc (
 347                                 unwrap_src, false, BufType.PACKET
 348                             );
 349                         } else {
 350                             /* Buffer not full, just need to read more
 351                              * data off the channel. Reset pointers
 352                              * for reading off SocketChannel
 353                              */
 354                             unwrap_src.position (unwrap_src.limit());
 355                             unwrap_src.limit (unwrap_src.capacity());
 356                         }
 357                         needData = true;
 358                     } else if (status == Status.BUFFER_OVERFLOW) {
 359                         r.buf = realloc (r.buf, true, BufType.APPLICATION);
 360                         needData = false;
 361                     } else if (status == Status.CLOSED) {
 362                         closed = true;
 363                         r.buf.flip();
 364                         return r;
 365                     }
 366                 } while (status != Status.OK);
 367             }
 368             u_remaining = unwrap_src.remaining();
 369             return r;
 370         }
 371     }
 372 
 373     /**
 374      * send the data in the given ByteBuffer. If a handshake is needed
 375      * then this is handled within this method. When this call returns,
 376      * all of the given user data has been sent and any handshake has been
 377      * completed. Caller should check if engine has been closed.
 378      */
 379     public WrapperResult sendData (ByteBuffer src) throws IOException {
 380         WrapperResult r=null;
 381         while (src.remaining() > 0) {
 382             r = wrapper.wrapAndSend(src);
 383             Status status = r.result.getStatus();
 384             if (status == Status.CLOSED) {
 385                 doClosure ();
 386                 return r;
 387             }
 388             HandshakeStatus hs_status = r.result.getHandshakeStatus();
 389             if (hs_status != HandshakeStatus.FINISHED &&
 390                 hs_status != HandshakeStatus.NOT_HANDSHAKING)
 391             {
 392                 doHandshake(hs_status);
 393             }
 394         }
 395         return r;
 396     }
 397 
 398     /**
 399      * read data thru the engine into the given ByteBuffer. If the
 400      * given buffer was not large enough, a new one is allocated
 401      * and returned. This call handles handshaking automatically.
 402      * Caller should check if engine has been closed.
 403      */
 404     public WrapperResult recvData (ByteBuffer dst) throws IOException {
 405         /* we wait until some user data arrives */
 406         WrapperResult r = null;
 407         assert dst.position() == 0;
 408         while (dst.position() == 0) {
 409             r = wrapper.recvAndUnwrap (dst);
 410             dst = (r.buf != dst) ? r.buf: dst;
 411             Status status = r.result.getStatus();
 412             if (status == Status.CLOSED) {
 413                 doClosure ();
 414                 return r;
 415             }
 416 
 417             HandshakeStatus hs_status = r.result.getHandshakeStatus();
 418             if (hs_status != HandshakeStatus.FINISHED &&
 419                 hs_status != HandshakeStatus.NOT_HANDSHAKING)
 420             {
 421                 doHandshake (hs_status);
 422             }
 423         }
 424         dst.flip();
 425         return r;
 426     }
 427 
 428     /* we've received a close notify. Need to call wrap to send
 429      * the response
 430      */
 431     void doClosure () throws IOException {
 432         try {
 433             handshaking.lock();
 434             ByteBuffer tmp = allocate(BufType.APPLICATION);
 435             WrapperResult r;
 436             do {
 437                 tmp.clear();
 438                 tmp.flip ();
 439                 r = wrapper.wrapAndSendX (tmp, true);
 440             } while (r.result.getStatus() != Status.CLOSED);
 441         } finally {
 442             handshaking.unlock();
 443         }
 444     }
 445 
 446     /* do the (complete) handshake after acquiring the handshake lock.
 447      * If two threads call this at the same time, then we depend
 448      * on the wrapper methods being idempotent. eg. if wrapAndSend()
 449      * is called with no data to send then there must be no problem
 450      */
 451     void doHandshake (HandshakeStatus hs_status) throws IOException {
 452         try {
 453             handshaking.lock();
 454             ByteBuffer tmp = allocate(BufType.APPLICATION);
 455             while (hs_status != HandshakeStatus.FINISHED &&
 456                    hs_status != HandshakeStatus.NOT_HANDSHAKING)
 457             {
 458                 WrapperResult r = null;
 459                 switch (hs_status) {
 460                     case NEED_TASK:
 461                         Runnable task;
 462                         while ((task = engine.getDelegatedTask()) != null) {
 463                             /* run in current thread, because we are already
 464                              * running an external Executor
 465                              */
 466                             task.run();
 467                         }
 468                         /* fall thru - call wrap again */
 469                     case NEED_WRAP:
 470                         tmp.clear();
 471                         tmp.flip();
 472                         r = wrapper.wrapAndSend(tmp);
 473                         break;
 474 
 475                     case NEED_UNWRAP:
 476                         tmp.clear();
 477                         r = wrapper.recvAndUnwrap (tmp);
 478                         if (r.buf != tmp) {
 479                             tmp = r.buf;
 480                         }
 481                         assert tmp.position() == 0;
 482                         break;
 483                 }
 484                 hs_status = r.result.getHandshakeStatus();
 485             }
 486         } finally {
 487             handshaking.unlock();
 488         }
 489     }
 490 
 491     /**
 492      * represents an SSL input stream. Multiple https requests can
 493      * be sent over one stream. closing this stream causes an SSL close
 494      * input.
 495      */
 496     class InputStream extends java.io.InputStream {
 497 
 498         ByteBuffer bbuf;
 499         boolean closed = false;
 500 
 501         /* this stream eof */
 502         boolean eof = false;
 503 
 504         boolean needData = true;
 505 
 506         InputStream () {
 507             bbuf = allocate (BufType.APPLICATION);
 508         }
 509 
 510         public int read (byte[] buf, int off, int len) throws IOException {
 511             if (closed) {
 512                 throw new IOException ("SSL stream is closed");
 513             }
 514             if (eof) {
 515                 return 0;
 516             }
 517             int available=0;
 518             if (!needData) {
 519                 available = bbuf.remaining();
 520                 needData = (available==0);
 521             }
 522             if (needData) {
 523                 bbuf.clear();
 524                 WrapperResult r = recvData (bbuf);
 525                 bbuf = r.buf== bbuf? bbuf: r.buf;
 526                 if ((available=bbuf.remaining()) == 0) {
 527                     eof = true;
 528                     return 0;
 529                 } else {
 530                     needData = false;
 531                 }
 532             }
 533             /* copy as much as possible from buf into users buf */
 534             if (len > available) {
 535                 len = available;
 536             }
 537             bbuf.get (buf, off, len);
 538             return len;
 539         }
 540 
 541         public int available () throws IOException {
 542             return bbuf.remaining();
 543         }
 544 
 545         public boolean markSupported () {
 546             return false; /* not possible with SSLEngine */
 547         }
 548 
 549         public void reset () throws IOException {
 550             throw new IOException ("mark/reset not supported");
 551         }
 552 
 553         public long skip (long s) throws IOException {
 554             int n = (int)s;
 555             if (closed) {
 556                 throw new IOException ("SSL stream is closed");
 557             }
 558             if (eof) {
 559                 return 0;
 560             }
 561             int ret = n;
 562             while (n > 0) {
 563                 if (bbuf.remaining() >= n) {
 564                     bbuf.position (bbuf.position()+n);
 565                     return ret;
 566                 } else {
 567                     n -= bbuf.remaining();
 568                     bbuf.clear();
 569                     WrapperResult r = recvData (bbuf);
 570                     bbuf = r.buf==bbuf? bbuf: r.buf;
 571                 }
 572             }
 573             return ret; /* not reached */
 574         }
 575 
 576         /**
 577          * close the SSL connection. All data must have been consumed
 578          * before this is called. Otherwise an exception will be thrown.
 579          * [Note. May need to revisit this. not quite the normal close() symantics
 580          */
 581         public void close () throws IOException {
 582             eof = true;
 583             engine.closeInbound ();
 584         }
 585 
 586         public int read (byte[] buf) throws IOException {
 587             return read (buf, 0, buf.length);
 588         }
 589 
 590         byte single[] = new byte [1];
 591 
 592         public int read () throws IOException {
 593             int n = read (single, 0, 1);
 594             if (n == 0) {
 595                 return -1;
 596             } else {
 597                 return single[0] & 0xFF;
 598             }
 599         }
 600     }
 601 
 602     /**
 603      * represents an SSL output stream. plain text data written to this stream
 604      * is encrypted by the stream. Multiple HTTPS responses can be sent on
 605      * one stream. closing this stream initiates an SSL closure
 606      */
 607     class OutputStream extends java.io.OutputStream {
 608         ByteBuffer buf;
 609         boolean closed = false;
 610         byte single[] = new byte[1];
 611 
 612         OutputStream() {
 613             buf = allocate(BufType.APPLICATION);
 614         }
 615 
 616         public void write(int b) throws IOException {
 617             single[0] = (byte)b;
 618             write (single, 0, 1);
 619         }
 620 
 621         public void write(byte b[]) throws IOException {
 622             write (b, 0, b.length);
 623         }
 624         public void write(byte b[], int off, int len) throws IOException {
 625             if (closed) {
 626                 throw new IOException ("output stream is closed");
 627             }
 628             while (len > 0) {
 629                 int l = len > buf.capacity() ? buf.capacity() : len;
 630                 buf.clear();
 631                 buf.put (b, off, l);
 632                 len -= l;
 633                 off += l;
 634                 buf.flip();
 635                 WrapperResult r = sendData (buf);
 636                 if (r.result.getStatus() == Status.CLOSED) {
 637                     closed = true;
 638                     if (len > 0) {
 639                         throw new IOException ("output stream is closed");
 640                     }
 641                 }
 642             }
 643         }
 644 
 645         public void flush() throws IOException {
 646             /* no-op */
 647         }
 648 
 649         public void close() throws IOException {
 650             WrapperResult r=null;
 651             engine.closeOutbound();
 652             closed = true;
 653             HandshakeStatus stat = HandshakeStatus.NEED_WRAP;
 654             buf.clear();
 655             while (stat == HandshakeStatus.NEED_WRAP) {
 656                 r = wrapper.wrapAndSend (buf);
 657                 stat = r.result.getHandshakeStatus();
 658             }
 659             assert r.result.getStatus() == Status.CLOSED;
 660         }
 661     }
 662 }