1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.http.nio.reactor.ssl;
29
30 import org.apache.http.annotation.ThreadSafe;
31 import org.apache.http.nio.reactor.EventMask;
32 import org.apache.http.nio.reactor.IOSession;
33 import org.apache.http.nio.reactor.SocketAccessor;
34 import org.apache.http.nio.reactor.SessionBufferStatus;
35
36 import javax.net.ssl.SSLContext;
37 import javax.net.ssl.SSLEngine;
38 import javax.net.ssl.SSLEngineResult;
39 import javax.net.ssl.SSLSession;
40 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
41 import javax.net.ssl.SSLEngineResult.Status;
42 import javax.net.ssl.SSLException;
43 import java.io.IOException;
44 import java.net.InetSocketAddress;
45 import java.net.Socket;
46 import java.net.SocketAddress;
47 import java.nio.ByteBuffer;
48 import java.nio.channels.ByteChannel;
49 import java.nio.channels.SelectionKey;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 @ThreadSafe
69 public class SSLIOSession implements IOSession, SessionBufferStatus, SocketAccessor {
70
71
72
73
74
75 public static final String SESSION_KEY = "http.session.ssl";
76
77 private final IOSession session;
78 private final SSLMode defaultMode;
79 private final SSLEngine sslEngine;
80 private final ByteBuffer inEncrypted;
81 private final ByteBuffer outEncrypted;
82 private final ByteBuffer inPlain;
83 private final ByteBuffer outPlain;
84 private final InternalByteChannel channel;
85 private final SSLSetupHandler handler;
86
87 private int appEventMask;
88 private SessionBufferStatus appBufferStatus;
89
90 private boolean endOfStream;
91 private volatile int status;
92 private volatile boolean initialized;
93
94
95
96
97
98
99
100
101
102 public SSLIOSession(
103 final IOSession session,
104 final SSLMode defaultMode,
105 final SSLContext sslContext,
106 final SSLSetupHandler handler) {
107 super();
108 if (session == null) {
109 throw new IllegalArgumentException("IO session may not be null");
110 }
111 if (sslContext == null) {
112 throw new IllegalArgumentException("SSL context may not be null");
113 }
114 this.session = session;
115 this.defaultMode = defaultMode;
116 this.appEventMask = session.getEventMask();
117 this.channel = new InternalByteChannel();
118 this.handler = handler;
119
120
121 this.session.setBufferStatus(this);
122
123 SocketAddress address = session.getRemoteAddress();
124 if (address instanceof InetSocketAddress) {
125 String hostname = ((InetSocketAddress) address).getHostName();
126 int port = ((InetSocketAddress) address).getPort();
127 this.sslEngine = sslContext.createSSLEngine(hostname, port);
128 } else {
129 this.sslEngine = sslContext.createSSLEngine();
130 }
131
132
133 int netBuffersize = this.sslEngine.getSession().getPacketBufferSize();
134 this.inEncrypted = ByteBuffer.allocate(netBuffersize);
135 this.outEncrypted = ByteBuffer.allocate(netBuffersize);
136
137
138 int appBuffersize = this.sslEngine.getSession().getApplicationBufferSize();
139 this.inPlain = ByteBuffer.allocate(appBuffersize);
140 this.outPlain = ByteBuffer.allocate(appBuffersize);
141 }
142
143 protected SSLSetupHandler getSSLSetupHandler() {
144 return this.handler;
145 }
146
147
148
149
150
151 public boolean isInitialized() {
152 return this.initialized;
153 }
154
155
156
157
158
159
160
161
162
163
164
165 public synchronized void initialize(final SSLMode mode) throws SSLException {
166 if (this.initialized) {
167 throw new IllegalStateException("SSL I/O session already initialized");
168 }
169 if (this.status >= IOSession.CLOSING) {
170 return;
171 }
172 switch (mode) {
173 case CLIENT:
174 this.sslEngine.setUseClientMode(true);
175 break;
176 case SERVER:
177 this.sslEngine.setUseClientMode(false);
178 break;
179 }
180 if (this.handler != null) {
181 this.handler.initalize(this.sslEngine);
182 }
183 this.initialized = true;
184 this.sslEngine.beginHandshake();
185 doHandshake();
186 }
187
188
189
190
191
192
193
194
195
196
197 public void initialize() throws SSLException {
198 initialize(this.defaultMode);
199 }
200
201 public synchronized SSLSession getSSLSession() {
202 return this.sslEngine.getSession();
203 }
204
205
206
207
208
209
210 private SSLException convert(final RuntimeException ex) throws SSLException {
211 Throwable cause = ex.getCause();
212 if (cause == null) {
213 cause = ex;
214 }
215 return new SSLException(cause);
216 }
217
218 private SSLEngineResult doWrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
219 try {
220 return this.sslEngine.wrap(src, dst);
221 } catch (RuntimeException ex) {
222 throw convert(ex);
223 }
224 }
225
226 private SSLEngineResult doUnwrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
227 try {
228 return this.sslEngine.unwrap(src, dst);
229 } catch (RuntimeException ex) {
230 throw convert(ex);
231 }
232 }
233
234 private void doRunTask() throws SSLException {
235 try {
236 Runnable r = this.sslEngine.getDelegatedTask();
237 if (r != null) {
238 r.run();
239 }
240 } catch (RuntimeException ex) {
241 throw convert(ex);
242 }
243 }
244
245 private void doHandshake() throws SSLException {
246 boolean handshaking = true;
247
248 SSLEngineResult result = null;
249 while (handshaking) {
250 switch (this.sslEngine.getHandshakeStatus()) {
251 case NEED_WRAP:
252
253 this.outPlain.flip();
254 result = doWrap(this.outPlain, this.outEncrypted);
255 this.outPlain.compact();
256 if (result.getStatus() != Status.OK) {
257 handshaking = false;
258 }
259 break;
260 case NEED_UNWRAP:
261
262 this.inEncrypted.flip();
263 result = doUnwrap(this.inEncrypted, this.inPlain);
264 this.inEncrypted.compact();
265 if (result.getStatus() != Status.OK) {
266 handshaking = false;
267 }
268 break;
269 case NEED_TASK:
270 doRunTask();
271 break;
272 case NOT_HANDSHAKING:
273 handshaking = false;
274 break;
275 case FINISHED:
276 break;
277 }
278 }
279
280
281
282
283 if (result != null && result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
284 if (this.handler != null) {
285 this.handler.verify(this.session, this.sslEngine.getSession());
286 }
287 }
288 }
289
290 private void updateEventMask() {
291
292 if (this.status == CLOSING && this.sslEngine.isOutboundDone()
293 && (this.endOfStream || this.sslEngine.isInboundDone())) {
294 this.status = CLOSED;
295 }
296
297 if (this.status == ACTIVE && this.endOfStream
298 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
299 this.status = CLOSED;
300 }
301 if (this.status == CLOSED) {
302 this.session.close();
303 return;
304 }
305
306 int oldMask = this.session.getEventMask();
307 int newMask = oldMask;
308 switch (this.sslEngine.getHandshakeStatus()) {
309 case NEED_WRAP:
310 newMask = EventMask.READ_WRITE;
311 break;
312 case NEED_UNWRAP:
313 newMask = EventMask.READ;
314 break;
315 case NOT_HANDSHAKING:
316 newMask = this.appEventMask;
317 break;
318 case NEED_TASK:
319 break;
320 case FINISHED:
321 break;
322 }
323
324
325 if (this.outEncrypted.position() > 0) {
326 newMask = newMask | EventMask.WRITE;
327 }
328
329
330 if (oldMask != newMask) {
331 this.session.setEventMask(newMask);
332 }
333 }
334
335 private int sendEncryptedData() throws IOException {
336 this.outEncrypted.flip();
337 int bytesWritten = this.session.channel().write(this.outEncrypted);
338 this.outEncrypted.compact();
339 return bytesWritten;
340 }
341
342 private int receiveEncryptedData() throws IOException {
343 if (this.endOfStream) {
344 return -1;
345 }
346 return this.session.channel().read(this.inEncrypted);
347 }
348
349 private boolean decryptData() throws SSLException {
350 boolean decrypted = false;
351 while (this.inEncrypted.position() > 0) {
352 this.inEncrypted.flip();
353 SSLEngineResult result = doUnwrap(this.inEncrypted, this.inPlain);
354 this.inEncrypted.compact();
355 if (result.getStatus() == Status.OK) {
356 decrypted = true;
357 } else {
358 break;
359 }
360 if (result.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) {
361 break;
362 }
363 if (this.endOfStream) {
364 break;
365 }
366 }
367 return decrypted;
368 }
369
370
371
372
373
374
375
376 public synchronized boolean isAppInputReady() throws IOException {
377 int bytesRead = receiveEncryptedData();
378 if (bytesRead == -1) {
379 this.endOfStream = true;
380 }
381 doHandshake();
382 HandshakeStatus status = this.sslEngine.getHandshakeStatus();
383 if (status == HandshakeStatus.NOT_HANDSHAKING || status == HandshakeStatus.FINISHED) {
384 decryptData();
385 }
386
387 return (this.appEventMask & SelectionKey.OP_READ) > 0
388 && (this.inPlain.position() > 0
389 || (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
390 || (this.endOfStream && this.status == ACTIVE));
391 }
392
393
394
395
396
397
398
399 public synchronized boolean isAppOutputReady() throws IOException {
400 return (this.appEventMask & SelectionKey.OP_WRITE) > 0
401 && this.status == ACTIVE
402 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
403 }
404
405
406
407
408
409
410 public synchronized void inboundTransport() throws IOException {
411 updateEventMask();
412 }
413
414
415
416
417
418
419 public synchronized void outboundTransport() throws IOException {
420 sendEncryptedData();
421 doHandshake();
422 updateEventMask();
423 }
424
425
426
427
428 public synchronized boolean isInboundDone() {
429 return this.sslEngine.isInboundDone();
430 }
431
432
433
434
435 public synchronized boolean isOutboundDone() {
436 return this.sslEngine.isOutboundDone();
437 }
438
439 private synchronized int writePlain(final ByteBuffer src) throws SSLException {
440 if (src == null) {
441 throw new IllegalArgumentException("Byte buffer may not be null");
442 }
443 if (this.status != ACTIVE) {
444 return -1;
445 }
446 if (this.outPlain.position() > 0) {
447 this.outPlain.flip();
448 doWrap(this.outPlain, this.outEncrypted);
449 this.outPlain.compact();
450 }
451 if (this.outPlain.position() == 0) {
452 SSLEngineResult result = doWrap(src, this.outEncrypted);
453 if (result.getStatus() == Status.CLOSED) {
454 this.status = CLOSED;
455 }
456 return result.bytesConsumed();
457 } else {
458 return 0;
459 }
460 }
461
462 private synchronized int readPlain(final ByteBuffer dst) {
463 if (dst == null) {
464 throw new IllegalArgumentException("Byte buffer may not be null");
465 }
466 if (this.inPlain.position() > 0) {
467 this.inPlain.flip();
468 int n = Math.min(this.inPlain.remaining(), dst.remaining());
469 for (int i = 0; i < n; i++) {
470 dst.put(this.inPlain.get());
471 }
472 this.inPlain.compact();
473 return n;
474 } else {
475 if (this.endOfStream) {
476 return -1;
477 } else {
478 return 0;
479 }
480 }
481 }
482
483 public synchronized void close() {
484 if (this.status >= CLOSING) {
485 return;
486 }
487 this.status = CLOSING;
488 this.sslEngine.closeOutbound();
489 updateEventMask();
490 }
491
492 public synchronized void shutdown() {
493 if (this.status == CLOSED) {
494 return;
495 }
496 this.status = CLOSED;
497 this.session.shutdown();
498 }
499
500 public int getStatus() {
501 return this.status;
502 }
503
504 public boolean isClosed() {
505 return this.status >= CLOSING || this.session.isClosed();
506 }
507
508 public ByteChannel channel() {
509 return this.channel;
510 }
511
512 public SocketAddress getLocalAddress() {
513 return this.session.getLocalAddress();
514 }
515
516 public SocketAddress getRemoteAddress() {
517 return this.session.getRemoteAddress();
518 }
519
520 public synchronized int getEventMask() {
521 return this.appEventMask;
522 }
523
524 public synchronized void setEventMask(int ops) {
525 this.appEventMask = ops;
526 updateEventMask();
527 }
528
529 public synchronized void setEvent(int op) {
530 this.appEventMask = this.appEventMask | op;
531 updateEventMask();
532 }
533
534 public synchronized void clearEvent(int op) {
535 this.appEventMask = this.appEventMask & ~op;
536 updateEventMask();
537 }
538
539 public int getSocketTimeout() {
540 return this.session.getSocketTimeout();
541 }
542
543 public void setSocketTimeout(int timeout) {
544 this.session.setSocketTimeout(timeout);
545 }
546
547 public synchronized boolean hasBufferedInput() {
548 return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
549 || this.inEncrypted.position() > 0
550 || this.inPlain.position() > 0;
551 }
552
553 public synchronized boolean hasBufferedOutput() {
554 return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedOutput())
555 || this.outEncrypted.position() > 0
556 || this.outPlain.position() > 0;
557 }
558
559 public synchronized void setBufferStatus(final SessionBufferStatus status) {
560 this.appBufferStatus = status;
561 }
562
563 public Object getAttribute(final String name) {
564 return this.session.getAttribute(name);
565 }
566
567 public Object removeAttribute(final String name) {
568 return this.session.removeAttribute(name);
569 }
570
571 public void setAttribute(final String name, final Object obj) {
572 this.session.setAttribute(name, obj);
573 }
574
575 private static void formatOps(final StringBuilder buffer, int ops) {
576 if ((ops & SelectionKey.OP_READ) > 0) {
577 buffer.append('r');
578 }
579 if ((ops & SelectionKey.OP_WRITE) > 0) {
580 buffer.append('w');
581 }
582 }
583
584 @Override
585 public synchronized String toString() {
586 StringBuilder buffer = new StringBuilder();
587 buffer.append(this.session);
588 buffer.append("[");
589 switch (this.status) {
590 case ACTIVE:
591 buffer.append("ACTIVE");
592 break;
593 case CLOSING:
594 buffer.append("CLOSING");
595 break;
596 case CLOSED:
597 buffer.append("CLOSED");
598 break;
599 }
600 buffer.append("][");
601 formatOps(buffer, this.appEventMask);
602 buffer.append("][");
603 buffer.append(this.sslEngine.getHandshakeStatus());
604 if (this.sslEngine.isInboundDone()) {
605 buffer.append("][inbound done][");
606 }
607 if (this.sslEngine.isOutboundDone()) {
608 buffer.append("][outbound done][");
609 }
610 if (this.endOfStream) {
611 buffer.append("][EOF][");
612 }
613 buffer.append("][");
614 buffer.append(this.inEncrypted.position());
615 buffer.append("][");
616 buffer.append(this.inPlain.position());
617 buffer.append("][");
618 buffer.append(this.outEncrypted.position());
619 buffer.append("][");
620 buffer.append(this.outPlain.position());
621 buffer.append("]");
622 return buffer.toString();
623 }
624
625 public Socket getSocket(){
626 if (this.session instanceof SocketAccessor){
627 return ((SocketAccessor) this.session).getSocket();
628 } else {
629 return null;
630 }
631 }
632
633 private class InternalByteChannel implements ByteChannel {
634
635 public int write(final ByteBuffer src) throws IOException {
636 return SSLIOSession.this.writePlain(src);
637 }
638
639 public int read(final ByteBuffer dst) throws IOException {
640 return SSLIOSession.this.readPlain(dst);
641 }
642
643 public void close() throws IOException {
644 SSLIOSession.this.close();
645 }
646
647 public boolean isOpen() {
648 return !SSLIOSession.this.isClosed();
649 }
650
651 }
652
653 }