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