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