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 java.io.IOException;
31 import java.net.Socket;
32 import java.net.SocketAddress;
33 import java.nio.ByteBuffer;
34 import java.nio.channels.ByteChannel;
35 import java.nio.channels.CancelledKeyException;
36 import java.nio.channels.ClosedChannelException;
37 import java.nio.channels.SelectionKey;
38 import java.util.concurrent.atomic.AtomicInteger;
39
40 import javax.net.ssl.SSLContext;
41 import javax.net.ssl.SSLEngine;
42 import javax.net.ssl.SSLEngineResult;
43 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
44 import javax.net.ssl.SSLEngineResult.Status;
45 import javax.net.ssl.SSLException;
46 import javax.net.ssl.SSLSession;
47
48 import org.apache.http.HttpHost;
49 import org.apache.http.annotation.Contract;
50 import org.apache.http.annotation.ThreadingBehavior;
51 import org.apache.http.nio.reactor.EventMask;
52 import org.apache.http.nio.reactor.IOSession;
53 import org.apache.http.nio.reactor.SessionBufferStatus;
54 import org.apache.http.nio.reactor.SocketAccessor;
55 import org.apache.http.util.Args;
56 import org.apache.http.util.Asserts;
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
76 public class SSLIOSession implements IOSession, SessionBufferStatus, SocketAccessor {
77
78
79
80
81
82 public static final String SESSION_KEY = "http.session.ssl";
83
84 private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
85
86 private final IOSession session;
87 private final SSLEngine sslEngine;
88 private final SSLBuffer inEncrypted;
89 private final SSLBuffer outEncrypted;
90 private final SSLBuffer inPlain;
91 private final InternalByteChannel channel;
92 private final SSLSetupHandler handler;
93 private final AtomicInteger outboundClosedCount;
94
95 private int appEventMask;
96 private SessionBufferStatus appBufferStatus;
97
98 private boolean endOfStream;
99 private volatile SSLMode sslMode;
100 private volatile int status;
101 private volatile boolean initialized;
102 private volatile boolean terminated;
103
104
105
106
107
108
109
110
111
112
113
114
115
116 public SSLIOSession(
117 final IOSession session,
118 final SSLMode sslMode,
119 final HttpHost host,
120 final SSLContext sslContext,
121 final SSLSetupHandler handler) {
122 this(session, sslMode, host, sslContext, handler, new PermanentSSLBufferManagementStrategy());
123 }
124
125
126
127
128
129
130
131
132
133
134
135 public SSLIOSession(
136 final IOSession session,
137 final SSLMode sslMode,
138 final HttpHost host,
139 final SSLContext sslContext,
140 final SSLSetupHandler handler,
141 final SSLBufferManagementStrategy bufferManagementStrategy) {
142 super();
143 Args.notNull(session, "IO session");
144 Args.notNull(sslContext, "SSL context");
145 Args.notNull(bufferManagementStrategy, "Buffer management strategy");
146 this.session = session;
147 this.sslMode = sslMode;
148 this.appEventMask = session.getEventMask();
149 this.channel = new InternalByteChannel();
150 this.handler = handler;
151
152
153 this.session.setBufferStatus(this);
154
155 if (this.sslMode == SSLMode.CLIENT && host != null) {
156 this.sslEngine = sslContext.createSSLEngine(host.getHostName(), host.getPort());
157 } else {
158 this.sslEngine = sslContext.createSSLEngine();
159 }
160
161
162 final int netBuffersize = this.sslEngine.getSession().getPacketBufferSize();
163 this.inEncrypted = bufferManagementStrategy.constructBuffer(netBuffersize);
164 this.outEncrypted = bufferManagementStrategy.constructBuffer(netBuffersize);
165
166
167 final int appBuffersize = this.sslEngine.getSession().getApplicationBufferSize();
168 this.inPlain = bufferManagementStrategy.constructBuffer(appBuffersize);
169 this.outboundClosedCount = new AtomicInteger(0);
170 }
171
172
173
174
175
176
177
178
179
180 public SSLIOSession(
181 final IOSession session,
182 final SSLMode sslMode,
183 final SSLContext sslContext,
184 final SSLSetupHandler handler) {
185 this(session, sslMode, null, sslContext, handler);
186 }
187
188 protected SSLSetupHandler getSSLSetupHandler() {
189 return this.handler;
190 }
191
192
193
194
195
196 public boolean isInitialized() {
197 return this.initialized;
198 }
199
200
201
202
203
204
205
206
207
208 @Deprecated
209 public synchronized void initialize(final SSLMode sslMode) throws SSLException {
210 this.sslMode = sslMode;
211 initialize();
212 }
213
214
215
216
217
218
219
220
221
222 public synchronized void initialize() throws SSLException {
223 Asserts.check(!this.initialized, "SSL I/O session already initialized");
224 if (this.status >= IOSession.CLOSING) {
225 return;
226 }
227 switch (this.sslMode) {
228 case CLIENT:
229 this.sslEngine.setUseClientMode(true);
230 break;
231 case SERVER:
232 this.sslEngine.setUseClientMode(false);
233 break;
234 }
235 if (this.handler != null) {
236 try {
237 this.handler.initalize(this.sslEngine);
238 } catch (final RuntimeException ex) {
239 throw convert(ex);
240 }
241 }
242 this.initialized = true;
243 this.sslEngine.beginHandshake();
244
245 this.inEncrypted.release();
246 this.outEncrypted.release();
247 this.inPlain.release();
248
249 doHandshake();
250 }
251
252 public synchronized SSLSession getSSLSession() {
253 return this.sslEngine.getSession();
254 }
255
256
257
258
259
260
261 private SSLException convert(final RuntimeException ex) {
262 Throwable cause = ex.getCause();
263 if (cause == null) {
264 cause = ex;
265 }
266 return new SSLException(cause);
267 }
268
269 private SSLEngineResult doWrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
270 try {
271 return this.sslEngine.wrap(src, dst);
272 } catch (final RuntimeException ex) {
273 throw convert(ex);
274 }
275 }
276
277 private SSLEngineResult doUnwrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
278 try {
279 return this.sslEngine.unwrap(src, dst);
280 } catch (final RuntimeException ex) {
281 throw convert(ex);
282 }
283 }
284
285 private void doRunTask() throws SSLException {
286 try {
287 final Runnable r = this.sslEngine.getDelegatedTask();
288 if (r != null) {
289 r.run();
290 }
291 } catch (final RuntimeException ex) {
292 throw convert(ex);
293 }
294 }
295
296 private void doHandshake() throws SSLException {
297 boolean handshaking = true;
298
299 SSLEngineResult result = null;
300 while (handshaking) {
301 HandshakeStatus handshakeStatus = this.sslEngine.getHandshakeStatus();
302
303
304
305
306 if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && outboundClosedCount.get() > 0) {
307 handshakeStatus = HandshakeStatus.NEED_WRAP;
308 }
309 switch (handshakeStatus) {
310 case NEED_WRAP:
311
312
313
314 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
315
316
317 result = doWrap(ByteBuffer.allocate(0), outEncryptedBuf);
318
319 if (result.getStatus() != Status.OK || result.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
320 handshaking = false;
321 }
322 break;
323 case NEED_UNWRAP:
324
325
326
327 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
328 final ByteBuffer inPlainBuf = this.inPlain.acquire();
329
330
331 inEncryptedBuf.flip();
332 try {
333 result = doUnwrap(inEncryptedBuf, inPlainBuf);
334 } finally {
335 inEncryptedBuf.compact();
336 }
337
338 try {
339 if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
340 throw new SSLException("Input buffer is full");
341 }
342 } finally {
343
344 if (inEncryptedBuf.position() == 0) {
345 this.inEncrypted.release();
346 }
347 }
348
349 if (this.status >= IOSession.CLOSING) {
350 this.inPlain.release();
351 }
352 if (result.getStatus() != Status.OK) {
353 handshaking = false;
354 }
355 break;
356 case NEED_TASK:
357 doRunTask();
358 break;
359 case NOT_HANDSHAKING:
360 handshaking = false;
361 break;
362 case FINISHED:
363 break;
364 }
365 }
366
367
368
369
370 if (result != null && result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
371 if (this.handler != null) {
372 try {
373 this.handler.verify(this.session, this.sslEngine.getSession());
374 } catch (final RuntimeException ex) {
375
376
377 throw convert(ex);
378 }
379 }
380 }
381 }
382
383 private void updateEventMask() {
384
385 if (this.status == ACTIVE
386 && (this.endOfStream || this.sslEngine.isInboundDone())) {
387 this.status = CLOSING;
388 }
389 if (this.status == CLOSING && !this.outEncrypted.hasData()) {
390 this.sslEngine.closeOutbound();
391 this.outboundClosedCount.incrementAndGet();
392 }
393 if (this.status == CLOSING && this.sslEngine.isOutboundDone()
394 && (this.endOfStream || this.sslEngine.isInboundDone())
395 && (this.terminated || (!this.inPlain.hasData() && (this.appBufferStatus == null || !this.appBufferStatus.hasBufferedInput())))) {
396 this.status = CLOSED;
397 }
398
399 if (this.status <= CLOSING && this.endOfStream
400 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
401 this.status = CLOSED;
402 }
403 if (this.status == CLOSED) {
404 this.session.close();
405 return;
406 }
407
408 final int oldMask = this.session.getEventMask();
409 int newMask = oldMask;
410 switch (this.sslEngine.getHandshakeStatus()) {
411 case NEED_WRAP:
412 newMask = EventMask.READ_WRITE;
413 break;
414 case NEED_UNWRAP:
415 newMask = EventMask.READ;
416 break;
417 case NOT_HANDSHAKING:
418 newMask = this.appEventMask;
419 break;
420 case NEED_TASK:
421 break;
422 case FINISHED:
423 break;
424 }
425
426 if (this.endOfStream &&
427 !this.inPlain.hasData() &&
428 (this.appBufferStatus == null || !this.appBufferStatus.hasBufferedInput())) {
429 newMask = newMask & ~EventMask.READ;
430 }
431
432
433 if (this.outEncrypted.hasData()) {
434 newMask = newMask | EventMask.WRITE;
435 } else if (this.sslEngine.isOutboundDone()) {
436 newMask = newMask & ~EventMask.WRITE;
437 }
438
439
440 if (oldMask != newMask) {
441 this.session.setEventMask(newMask);
442 }
443 }
444
445 private int sendEncryptedData() throws IOException {
446 if (!this.outEncrypted.hasData()) {
447
448
449
450
451 return this.session.channel().write(EMPTY_BUFFER);
452 }
453
454
455 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
456
457 final int bytesWritten;
458
459 outEncryptedBuf.flip();
460 try {
461 bytesWritten = this.session.channel().write(outEncryptedBuf);
462 } finally {
463 outEncryptedBuf.compact();
464 }
465
466
467 if (outEncryptedBuf.position() == 0) {
468 this.outEncrypted.release();
469 }
470 return bytesWritten;
471 }
472
473 private int receiveEncryptedData() throws IOException {
474 if (this.endOfStream) {
475 return -1;
476 }
477
478
479 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
480
481
482 final int bytesRead = this.session.channel().read(inEncryptedBuf);
483
484
485 if (inEncryptedBuf.position() == 0) {
486 this.inEncrypted.release();
487 }
488 if (bytesRead == -1) {
489 this.endOfStream = true;
490 }
491 return bytesRead;
492 }
493
494 private boolean decryptData() throws SSLException {
495 boolean decrypted = false;
496 while (this.inEncrypted.hasData()) {
497
498 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
499 final ByteBuffer inPlainBuf = this.inPlain.acquire();
500
501 final SSLEngineResult result;
502
503 inEncryptedBuf.flip();
504 try {
505 result = doUnwrap(inEncryptedBuf, inPlainBuf);
506 } finally {
507 inEncryptedBuf.compact();
508 }
509
510 try {
511 if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
512 throw new SSLException("Unable to complete SSL handshake");
513 }
514 final Status status = result.getStatus();
515 if (status == Status.OK) {
516 decrypted = true;
517 } else {
518 if (status == Status.BUFFER_UNDERFLOW && this.endOfStream) {
519 throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream");
520 }
521 break;
522 }
523 } finally {
524
525 if (this.inEncrypted.acquire().position() == 0) {
526 this.inEncrypted.release();
527 }
528 }
529 }
530 if (this.sslEngine.isInboundDone()) {
531 this.endOfStream = true;
532 }
533 return decrypted;
534 }
535
536
537
538
539
540
541
542 public synchronized boolean isAppInputReady() throws IOException {
543 do {
544 receiveEncryptedData();
545 doHandshake();
546 final HandshakeStatus status = this.sslEngine.getHandshakeStatus();
547 if (status == HandshakeStatus.NOT_HANDSHAKING || status == HandshakeStatus.FINISHED) {
548 decryptData();
549 }
550 if (this.terminated) {
551
552 final ByteBuffer inPlainBuf = this.inPlain.acquire();
553 inPlainBuf.clear();
554 if (inPlainBuf.position() == 0) {
555 this.inPlain.release();
556 }
557 }
558 } while (this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK);
559
560 return (this.appEventMask & SelectionKey.OP_READ) > 0
561 && (this.inPlain.hasData()
562 || (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
563 || (this.endOfStream && this.status == ACTIVE));
564 }
565
566
567
568
569
570
571
572 public synchronized boolean isAppOutputReady() throws IOException {
573 return (this.appEventMask & SelectionKey.OP_WRITE) > 0
574 && this.status == ACTIVE
575 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
576 }
577
578
579
580
581
582
583 public synchronized void inboundTransport() throws IOException {
584 updateEventMask();
585 }
586
587
588
589
590
591
592 public synchronized void outboundTransport() throws IOException {
593 sendEncryptedData();
594 doHandshake();
595 updateEventMask();
596 }
597
598
599
600
601 public synchronized boolean isInboundDone() {
602 return this.sslEngine.isInboundDone();
603 }
604
605
606
607
608 public synchronized boolean isOutboundDone() {
609 return this.sslEngine.isOutboundDone();
610 }
611
612 private synchronized int writePlain(final ByteBuffer src) throws IOException {
613 Args.notNull(src, "Byte buffer");
614 if (this.status != ACTIVE) {
615 throw new ClosedChannelException();
616 }
617 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
618 final SSLEngineResult result = doWrap(src, outEncryptedBuf);
619 if (result.getStatus() == Status.CLOSED) {
620 this.status = CLOSED;
621 }
622 return result.bytesConsumed();
623 }
624
625 private synchronized int readPlain(final ByteBuffer dst) {
626 Args.notNull(dst, "Byte buffer");
627 if (this.inPlain.hasData()) {
628
629 final ByteBuffer inPlainBuf = this.inPlain.acquire();
630
631
632 inPlainBuf.flip();
633 final int n = Math.min(inPlainBuf.remaining(), dst.remaining());
634 for (int i = 0; i < n; i++) {
635 dst.put(inPlainBuf.get());
636 }
637 inPlainBuf.compact();
638
639
640 if (inPlainBuf.position() == 0) {
641 this.inPlain.release();
642 }
643 return n;
644 }
645 return this.endOfStream ? -1 : 0;
646 }
647
648 @Override
649 public synchronized void close() {
650 this.terminated = true;
651 if (this.status >= CLOSING) {
652 return;
653 }
654 this.status = CLOSING;
655 if (this.session.getSocketTimeout() == 0) {
656 this.session.setSocketTimeout(1000);
657 }
658
659 try {
660 updateEventMask();
661 } catch (final CancelledKeyException ex) {
662 shutdown();
663 }
664 }
665
666 @Override
667 public synchronized void shutdown() {
668 if (this.status == CLOSED) {
669 return;
670 }
671 this.status = CLOSED;
672 this.session.shutdown();
673
674 this.inEncrypted.release();
675 this.outEncrypted.release();
676 this.inPlain.release();
677
678 }
679
680 @Override
681 public int getStatus() {
682 return this.status;
683 }
684
685 @Override
686 public boolean isClosed() {
687 return this.status >= CLOSING || this.session.isClosed();
688 }
689
690 @Override
691 public ByteChannel channel() {
692 return this.channel;
693 }
694
695 @Override
696 public SocketAddress getLocalAddress() {
697 return this.session.getLocalAddress();
698 }
699
700 @Override
701 public SocketAddress getRemoteAddress() {
702 return this.session.getRemoteAddress();
703 }
704
705 @Override
706 public synchronized int getEventMask() {
707 return this.appEventMask;
708 }
709
710 @Override
711 public synchronized void setEventMask(final int ops) {
712 this.appEventMask = ops;
713 updateEventMask();
714 }
715
716 @Override
717 public synchronized void setEvent(final int op) {
718 this.appEventMask = this.appEventMask | op;
719 updateEventMask();
720 }
721
722 @Override
723 public synchronized void clearEvent(final int op) {
724 this.appEventMask = this.appEventMask & ~op;
725 updateEventMask();
726 }
727
728 @Override
729 public int getSocketTimeout() {
730 return this.session.getSocketTimeout();
731 }
732
733 @Override
734 public void setSocketTimeout(final int timeout) {
735 this.session.setSocketTimeout(timeout);
736 }
737
738 @Override
739 public synchronized boolean hasBufferedInput() {
740 return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
741 || this.inEncrypted.hasData()
742 || this.inPlain.hasData();
743 }
744
745 @Override
746 public synchronized boolean hasBufferedOutput() {
747 return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedOutput())
748 || this.outEncrypted.hasData();
749 }
750
751 @Override
752 public synchronized void setBufferStatus(final SessionBufferStatus status) {
753 this.appBufferStatus = status;
754 }
755
756 @Override
757 public Object getAttribute(final String name) {
758 return this.session.getAttribute(name);
759 }
760
761 @Override
762 public Object removeAttribute(final String name) {
763 return this.session.removeAttribute(name);
764 }
765
766 @Override
767 public void setAttribute(final String name, final Object obj) {
768 this.session.setAttribute(name, obj);
769 }
770
771 private static void formatOps(final StringBuilder buffer, final int ops) {
772 if ((ops & SelectionKey.OP_READ) > 0) {
773 buffer.append('r');
774 }
775 if ((ops & SelectionKey.OP_WRITE) > 0) {
776 buffer.append('w');
777 }
778 }
779
780 @Override
781 public String toString() {
782 final StringBuilder buffer = new StringBuilder();
783 buffer.append(this.session);
784 buffer.append("[");
785 switch (this.status) {
786 case ACTIVE:
787 buffer.append("ACTIVE");
788 break;
789 case CLOSING:
790 buffer.append("CLOSING");
791 break;
792 case CLOSED:
793 buffer.append("CLOSED");
794 break;
795 }
796 buffer.append("][");
797 formatOps(buffer, this.appEventMask);
798 buffer.append("][");
799 buffer.append(this.sslEngine.getHandshakeStatus());
800 if (this.sslEngine.isInboundDone()) {
801 buffer.append("][inbound done][");
802 }
803 if (this.sslEngine.isOutboundDone()) {
804 buffer.append("][outbound done][");
805 }
806 if (this.endOfStream) {
807 buffer.append("][EOF][");
808 }
809 buffer.append("][");
810 buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position());
811 buffer.append("][");
812 buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position());
813 buffer.append("][");
814 buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position());
815 buffer.append("]");
816 return buffer.toString();
817 }
818
819 @Override
820 public Socket getSocket(){
821 return this.session instanceof SocketAccessor/../../../../../org/apache/http/nio/reactor/SocketAccessor.html#SocketAccessor">SocketAccessor ? ((SocketAccessor) this.session).getSocket() : null;
822 }
823
824 private class InternalByteChannel implements ByteChannel {
825
826 @Override
827 public int write(final ByteBuffer src) throws IOException {
828 return SSLIOSession.this.writePlain(src);
829 }
830
831 @Override
832 public int read(final ByteBuffer dst) throws IOException {
833 return SSLIOSession.this.readPlain(dst);
834 }
835
836 @Override
837 public void close() throws IOException {
838 SSLIOSession.this.close();
839 }
840
841 @Override
842 public boolean isOpen() {
843 return !SSLIOSession.this.isClosed();
844 }
845
846 }
847
848 }