< 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 >