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