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 }