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