1 /*
   2  * Copyright (c) 2015, 2017, 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 jdk.incubator.http;
  27 
  28 import java.io.IOException;
  29 import java.nio.ByteBuffer;
  30 import java.nio.channels.SocketChannel;
  31 import java.util.Arrays;
  32 import java.util.List;
  33 import java.util.concurrent.locks.Lock;
  34 import java.util.concurrent.locks.ReentrantLock;
  35 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
  36 import javax.net.ssl.SSLEngineResult.Status;
  37 import javax.net.ssl.*;
  38 import jdk.incubator.http.internal.common.Log;
  39 import jdk.incubator.http.internal.common.Utils;
  40 import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
  41 
  42 /**
  43  * Implements the mechanics of SSL by managing an SSLEngine object.
  44  * One of these is associated with each SSLConnection.
  45  */
  46 class SSLDelegate {
  47 
  48     final SSLEngine engine;
  49     final EngineWrapper wrapper;
  50     final Lock handshaking = new ReentrantLock();
  51     final SSLParameters sslParameters;
  52     final SocketChannel chan;
  53     final HttpClientImpl client;
  54     final String serverName;
  55 
  56     SSLDelegate(SSLEngine eng, SocketChannel chan, HttpClientImpl client, String sn)
  57     {
  58         this.engine = eng;
  59         this.chan = chan;
  60         this.client = client;
  61         this.wrapper = new EngineWrapper(chan, engine);
  62         this.sslParameters = engine.getSSLParameters();
  63         this.serverName = sn;
  64     }
  65 
  66     // alpn[] may be null
  67     SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn, String sn)
  68         throws IOException
  69     {
  70         serverName = sn;
  71         SSLContext context = client.sslContext();
  72         engine = context.createSSLEngine();
  73         engine.setUseClientMode(true);
  74         SSLParameters sslp = client.sslParameters()
  75                                    .orElseGet(context::getSupportedSSLParameters);
  76         sslParameters = Utils.copySSLParameters(sslp);
  77         if (sn != null) {
  78             SNIHostName sni = new SNIHostName(sn);
  79             sslParameters.setServerNames(List.of(sni));
  80         }
  81         if (alpn != null) {
  82             sslParameters.setApplicationProtocols(alpn);
  83             Log.logSSL("SSLDelegate: Setting application protocols: {0}" + Arrays.toString(alpn));
  84         } else {
  85             Log.logSSL("SSLDelegate: No application protocols proposed");
  86         }
  87         engine.setSSLParameters(sslParameters);
  88         wrapper = new EngineWrapper(chan, engine);
  89         this.chan = chan;
  90         this.client = client;
  91     }
  92 
  93     SSLParameters getSSLParameters() {
  94         return sslParameters;
  95     }
  96 
  97     private static long countBytes(ByteBuffer[] buffers, int start, int number) {
  98         long c = 0;
  99         for (int i=0; i<number; i++) {
 100             c+= buffers[start+i].remaining();
 101         }
 102         return c;
 103     }
 104 
 105 
 106     static class WrapperResult {
 107         static WrapperResult createOK() {
 108             WrapperResult r = new WrapperResult();
 109             r.buf = null;
 110             r.result = new SSLEngineResult(Status.OK, NOT_HANDSHAKING, 0, 0);
 111             return r;
 112         }
 113         SSLEngineResult result;
 114 
 115         ByteBuffer buf; // buffer containing result data
 116     }
 117 
 118     int app_buf_size;
 119     int packet_buf_size;
 120 
 121     enum BufType {
 122         PACKET,
 123         APPLICATION
 124     }
 125 
 126     ByteBuffer allocate (BufType type) {
 127         return allocate (type, -1);
 128     }
 129 
 130     // TODO: Use buffer pool for this
 131     ByteBuffer allocate (BufType type, int len) {
 132         assert engine != null;
 133         synchronized (this) {
 134             int size;
 135             if (type == BufType.PACKET) {
 136                 if (packet_buf_size == 0) {
 137                     SSLSession sess = engine.getSession();
 138                     packet_buf_size = sess.getPacketBufferSize();
 139                 }
 140                 if (len > packet_buf_size) {
 141                     packet_buf_size = len;
 142                 }
 143                 size = packet_buf_size;
 144             } else {
 145                 if (app_buf_size == 0) {
 146                     SSLSession sess = engine.getSession();
 147                     app_buf_size = sess.getApplicationBufferSize();
 148                 }
 149                 if (len > app_buf_size) {
 150                     app_buf_size = len;
 151                 }
 152                 size = app_buf_size;
 153             }
 154             return ByteBuffer.allocate (size);
 155         }
 156     }
 157 
 158     /* reallocates the buffer by :-
 159      * 1. creating a new buffer double the size of the old one
 160      * 2. putting the contents of the old buffer into the new one
 161      * 3. set xx_buf_size to the new size if it was smaller than new size
 162      *
 163      * flip is set to true if the old buffer needs to be flipped
 164      * before it is copied.
 165      */
 166     private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) {
 167         // TODO: there should be the linear growth, rather than exponential as
 168         // we definitely know the maximum amount of space required to unwrap
 169         synchronized (this) {
 170             int nsize = 2 * b.capacity();
 171             ByteBuffer n = allocate (type, nsize);
 172             if (flip) {
 173                 b.flip();
 174             }
 175             n.put(b);
 176             b = n;
 177         }
 178         return b;
 179     }
 180 
 181     /**
 182      * This is a thin wrapper over SSLEngine and the SocketChannel, which
 183      * guarantees the ordering of wraps/unwraps with respect to the underlying
 184      * channel read/writes. It handles the UNDER/OVERFLOW status codes
 185      * It does not handle the handshaking status codes, or the CLOSED status code
 186      * though once the engine is closed, any attempt to read/write to it
 187      * will get an exception.  The overall result is returned.
 188      * It functions synchronously/blocking
 189      */
 190     class EngineWrapper {
 191 
 192         SocketChannel chan;
 193         SSLEngine engine;
 194         Object wrapLock, unwrapLock;
 195         ByteBuffer unwrap_src, wrap_dst;
 196         boolean closed = false;
 197         int u_remaining; // the number of bytes left in unwrap_src after an unwrap()
 198 
 199         EngineWrapper (SocketChannel chan, SSLEngine engine) {
 200             this.chan = chan;
 201             this.engine = engine;
 202             wrapLock = new Object();
 203             unwrapLock = new Object();
 204             unwrap_src = allocate(BufType.PACKET);
 205             wrap_dst = allocate(BufType.PACKET);
 206         }
 207 
 208         void close () throws IOException {
 209         }
 210 
 211         WrapperResult wrapAndSend(ByteBuffer src, boolean ignoreClose)
 212             throws IOException
 213         {
 214             ByteBuffer[] buffers = new ByteBuffer[1];
 215             buffers[0] = src;
 216             return wrapAndSend(buffers, 0, 1, ignoreClose);
 217         }
 218 
 219         /* try to wrap and send the data in src. Handles OVERFLOW.
 220          * Might block if there is an outbound blockage or if another
 221          * thread is calling wrap(). Also, might not send any data
 222          * if an unwrap is needed.
 223          */
 224         WrapperResult wrapAndSend(ByteBuffer[] src,
 225                                   int offset,
 226                                   int len,
 227                                   boolean ignoreClose)
 228             throws IOException
 229         {
 230             if (closed && !ignoreClose) {
 231                 throw new IOException ("Engine is closed");
 232             }
 233             Status status;
 234             WrapperResult r = new WrapperResult();
 235             synchronized (wrapLock) {
 236                 wrap_dst.clear();
 237                 do {
 238                     r.result = engine.wrap (src, offset, len, wrap_dst);
 239                     status = r.result.getStatus();
 240                     if (status == Status.BUFFER_OVERFLOW) {
 241                         wrap_dst = realloc (wrap_dst, true, BufType.PACKET);
 242                     }
 243                 } while (status == Status.BUFFER_OVERFLOW);
 244                 if (status == Status.CLOSED && !ignoreClose) {
 245                     closed = true;
 246                     return r;
 247                 }
 248                 if (r.result.bytesProduced() > 0) {
 249                     wrap_dst.flip();
 250                     int l = wrap_dst.remaining();
 251                     assert l == r.result.bytesProduced();
 252                     while (l>0) {
 253                         l -= chan.write (wrap_dst);
 254                     }
 255                 }
 256             }
 257             return r;
 258         }
 259 
 260         /* block until a complete message is available and return it
 261          * in dst, together with the Result. dst may have been re-allocated
 262          * so caller should check the returned value in Result
 263          * If handshaking is in progress then, possibly no data is returned
 264          */
 265         WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException {
 266             Status status;
 267             WrapperResult r = new WrapperResult();
 268             r.buf = dst;
 269             if (closed) {
 270                 throw new IOException ("Engine is closed");
 271             }
 272             boolean needData;
 273             if (u_remaining > 0) {
 274                 unwrap_src.compact();
 275                 unwrap_src.flip();
 276                 needData = false;
 277             } else {
 278                 unwrap_src.clear();
 279                 needData = true;
 280             }
 281             synchronized (unwrapLock) {
 282                 int x;
 283                 do {
 284                     if (needData) {
 285                         x = chan.read (unwrap_src);
 286                         if (x == -1) {
 287                             throw new IOException ("connection closed for reading");
 288                         }
 289                         unwrap_src.flip();
 290                     }
 291                     r.result = engine.unwrap (unwrap_src, r.buf);
 292                     status = r.result.getStatus();
 293                     if (status == Status.BUFFER_UNDERFLOW) {
 294                         if (unwrap_src.limit() == unwrap_src.capacity()) {
 295                             /* buffer not big enough */
 296                             unwrap_src = realloc (
 297                                 unwrap_src, false, BufType.PACKET
 298                             );
 299                         } else {
 300                             /* Buffer not full, just need to read more
 301                              * data off the channel. Reset pointers
 302                              * for reading off SocketChannel
 303                              */
 304                             unwrap_src.position (unwrap_src.limit());
 305                             unwrap_src.limit (unwrap_src.capacity());
 306                         }
 307                         needData = true;
 308                     } else if (status == Status.BUFFER_OVERFLOW) {
 309                         r.buf = realloc (r.buf, true, BufType.APPLICATION);
 310                         needData = false;
 311                     } else if (status == Status.CLOSED) {
 312                         closed = true;
 313                         r.buf.flip();
 314                         return r;
 315                     }
 316                 } while (status != Status.OK);
 317             }
 318             u_remaining = unwrap_src.remaining();
 319             return r;
 320         }
 321     }
 322 
 323     WrapperResult sendData (ByteBuffer src) throws IOException {
 324         ByteBuffer[] buffers = new ByteBuffer[1];
 325         buffers[0] = src;
 326         return sendData(buffers, 0, 1);
 327     }
 328 
 329     /**
 330      * send the data in the given ByteBuffer. If a handshake is needed
 331      * then this is handled within this method. When this call returns,
 332      * all of the given user data has been sent and any handshake has been
 333      * completed. Caller should check if engine has been closed.
 334      */
 335     WrapperResult sendData (ByteBuffer[] src, int offset, int len) throws IOException {
 336         WrapperResult r = WrapperResult.createOK();
 337         while (countBytes(src, offset, len) > 0) {
 338             r = wrapper.wrapAndSend(src, offset, len, false);
 339             Status status = r.result.getStatus();
 340             if (status == Status.CLOSED) {
 341                 doClosure ();
 342                 return r;
 343             }
 344             HandshakeStatus hs_status = r.result.getHandshakeStatus();
 345             if (hs_status != HandshakeStatus.FINISHED &&
 346                 hs_status != HandshakeStatus.NOT_HANDSHAKING)
 347             {
 348                 doHandshake(hs_status);
 349             }
 350         }
 351         return r;
 352     }
 353 
 354     /**
 355      * read data thru the engine into the given ByteBuffer. If the
 356      * given buffer was not large enough, a new one is allocated
 357      * and returned. This call handles handshaking automatically.
 358      * Caller should check if engine has been closed.
 359      */
 360     WrapperResult recvData (ByteBuffer dst) throws IOException {
 361         /* we wait until some user data arrives */
 362         int mark = dst.position();
 363         WrapperResult r = null;
 364         int pos = dst.position();
 365         while (dst.position() == pos) {
 366             r = wrapper.recvAndUnwrap (dst);
 367             dst = (r.buf != dst) ? r.buf: dst;
 368             Status status = r.result.getStatus();
 369             if (status == Status.CLOSED) {
 370                 doClosure ();
 371                 return r;
 372             }
 373 
 374             HandshakeStatus hs_status = r.result.getHandshakeStatus();
 375             if (hs_status != HandshakeStatus.FINISHED &&
 376                 hs_status != HandshakeStatus.NOT_HANDSHAKING)
 377             {
 378                 doHandshake (hs_status);
 379             }
 380         }
 381         Utils.flipToMark(dst, mark);
 382         return r;
 383     }
 384 
 385     /* we've received a close notify. Need to call wrap to send
 386      * the response
 387      */
 388     void doClosure () throws IOException {
 389         try {
 390             handshaking.lock();
 391             ByteBuffer tmp = allocate(BufType.APPLICATION);
 392             WrapperResult r;
 393             do {
 394                 tmp.clear();
 395                 tmp.flip ();
 396                 r = wrapper.wrapAndSend(tmp, true);
 397             } while (r.result.getStatus() != Status.CLOSED);
 398         } finally {
 399             handshaking.unlock();
 400         }
 401     }
 402 
 403     /* do the (complete) handshake after acquiring the handshake lock.
 404      * If two threads call this at the same time, then we depend
 405      * on the wrapper methods being idempotent. eg. if wrapAndSend()
 406      * is called with no data to send then there must be no problem
 407      */
 408     @SuppressWarnings("fallthrough")
 409     void doHandshake (HandshakeStatus hs_status) throws IOException {
 410         boolean wasBlocking = false;
 411         try {
 412             wasBlocking = chan.isBlocking();
 413             handshaking.lock();
 414             chan.configureBlocking(true);
 415             ByteBuffer tmp = allocate(BufType.APPLICATION);
 416             while (hs_status != HandshakeStatus.FINISHED &&
 417                    hs_status != HandshakeStatus.NOT_HANDSHAKING)
 418             {
 419                 WrapperResult r = null;
 420                 switch (hs_status) {
 421                     case NEED_TASK:
 422                         Runnable task;
 423                         while ((task = engine.getDelegatedTask()) != null) {
 424                             /* run in current thread, because we are already
 425                              * running an external Executor
 426                              */
 427                             task.run();
 428                         }
 429                         /* fall thru - call wrap again */
 430                     case NEED_WRAP:
 431                         tmp.clear();
 432                         tmp.flip();
 433                         r = wrapper.wrapAndSend(tmp, false);
 434                         break;
 435 
 436                     case NEED_UNWRAP:
 437                         tmp.clear();
 438                         r = wrapper.recvAndUnwrap (tmp);
 439                         if (r.buf != tmp) {
 440                             tmp = r.buf;
 441                         }
 442                         assert tmp.position() == 0;
 443                         break;
 444                 }
 445                 hs_status = r.result.getHandshakeStatus();
 446             }
 447             Log.logSSL(getSessionInfo());
 448             if (!wasBlocking) {
 449                 chan.configureBlocking(false);
 450             }
 451         } finally {
 452             handshaking.unlock();
 453         }
 454     }
 455 
 456     static void printParams(SSLParameters p) {
 457         System.out.println("SSLParameters:");
 458         if (p == null) {
 459             System.out.println("Null params");
 460             return;
 461         }
 462         for (String cipher : p.getCipherSuites()) {
 463                 System.out.printf("cipher: %s\n", cipher);
 464         }
 465         // JDK 8 EXCL START
 466         for (String approto : p.getApplicationProtocols()) {
 467                 System.out.printf("application protocol: %s\n", approto);
 468         }
 469         // JDK 8 EXCL END
 470         for (String protocol : p.getProtocols()) {
 471                 System.out.printf("protocol: %s\n", protocol);
 472         }
 473         if (p.getServerNames() != null) {
 474             for (SNIServerName sname : p.getServerNames()) {
 475                 System.out.printf("server name: %s\n", sname.toString());
 476             }
 477         }
 478     }
 479 
 480     String getSessionInfo() {
 481         StringBuilder sb = new StringBuilder();
 482         String application = engine.getApplicationProtocol();
 483         SSLSession sess = engine.getSession();
 484         String cipher = sess.getCipherSuite();
 485         String protocol = sess.getProtocol();
 486         sb.append("Handshake complete alpn: ")
 487                 .append(application)
 488                 .append(", Cipher: ")
 489                 .append(cipher)
 490                 .append(", Protocol: ")
 491                 .append(protocol);
 492         return sb.toString();
 493     }
 494 }