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 try {
372 this.handler.verify(this.session, this.sslEngine.getSession());
373 } catch (final RuntimeException ex) {
374
375
376 throw convert(ex);
377 }
378 }
379 }
380 }
381
382 private void updateEventMask() {
383
384 if (this.status == ACTIVE
385 && (this.endOfStream || this.sslEngine.isInboundDone())) {
386 this.status = CLOSING;
387 }
388 if (this.status == CLOSING && !this.outEncrypted.hasData()) {
389 this.sslEngine.closeOutbound();
390 this.outboundClosedCount.incrementAndGet();
391 }
392 if (this.status == CLOSING && this.sslEngine.isOutboundDone()
393 && (this.endOfStream || this.sslEngine.isInboundDone())
394 && !this.inPlain.hasData()
395 && 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 } else if (this.status == CLOSING) {
431 newMask = newMask | EventMask.READ;
432 }
433
434
435 if (this.outEncrypted.hasData()) {
436 newMask = newMask | EventMask.WRITE;
437 } else if (this.sslEngine.isOutboundDone()) {
438 newMask = newMask & ~EventMask.WRITE;
439 }
440
441
442 if (oldMask != newMask) {
443 this.session.setEventMask(newMask);
444 }
445 }
446
447 private int sendEncryptedData() throws IOException {
448 if (!this.outEncrypted.hasData()) {
449
450
451
452
453 return this.session.channel().write(EMPTY_BUFFER);
454 }
455
456
457 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
458
459 final int bytesWritten;
460
461 outEncryptedBuf.flip();
462 try {
463 bytesWritten = this.session.channel().write(outEncryptedBuf);
464 } finally {
465 outEncryptedBuf.compact();
466 }
467
468
469 if (outEncryptedBuf.position() == 0) {
470 this.outEncrypted.release();
471 }
472 return bytesWritten;
473 }
474
475 private int receiveEncryptedData() throws IOException {
476 if (this.endOfStream) {
477 return -1;
478 }
479
480
481 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
482
483
484 final int bytesRead = this.session.channel().read(inEncryptedBuf);
485
486
487 if (inEncryptedBuf.position() == 0) {
488 this.inEncrypted.release();
489 }
490 if (bytesRead == -1) {
491 this.endOfStream = true;
492 }
493 return bytesRead;
494 }
495
496 private boolean decryptData() throws SSLException {
497 boolean decrypted = false;
498 while (this.inEncrypted.hasData()) {
499
500 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
501 final ByteBuffer inPlainBuf = this.inPlain.acquire();
502
503 final SSLEngineResult result;
504
505 inEncryptedBuf.flip();
506 try {
507 result = doUnwrap(inEncryptedBuf, inPlainBuf);
508 } finally {
509 inEncryptedBuf.compact();
510 }
511
512 try {
513 if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
514 throw new SSLException("Unable to complete SSL handshake");
515 }
516 final Status status = result.getStatus();
517 if (status == Status.OK) {
518 decrypted = true;
519 } else {
520 if (status == Status.BUFFER_UNDERFLOW && this.endOfStream) {
521 throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream");
522 }
523 break;
524 }
525 } finally {
526
527 if (this.inEncrypted.acquire().position() == 0) {
528 this.inEncrypted.release();
529 }
530 }
531 }
532 if (this.sslEngine.isInboundDone()) {
533 this.endOfStream = true;
534 }
535 return decrypted;
536 }
537
538
539
540
541
542
543
544 public synchronized boolean isAppInputReady() throws IOException {
545 do {
546 receiveEncryptedData();
547 doHandshake();
548 final HandshakeStatus status = this.sslEngine.getHandshakeStatus();
549 if (status == HandshakeStatus.NOT_HANDSHAKING || status == HandshakeStatus.FINISHED) {
550 decryptData();
551 }
552 } while (this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK);
553
554 return (this.appEventMask & SelectionKey.OP_READ) > 0
555 && (this.inPlain.hasData()
556 || (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
557 || (this.endOfStream && this.status == ACTIVE));
558 }
559
560
561
562
563
564
565
566 public synchronized boolean isAppOutputReady() throws IOException {
567 return (this.appEventMask & SelectionKey.OP_WRITE) > 0
568 && this.status == ACTIVE
569 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
570 }
571
572
573
574
575
576
577 public synchronized void inboundTransport() throws IOException {
578 updateEventMask();
579 }
580
581
582
583
584
585
586 public synchronized void outboundTransport() throws IOException {
587 sendEncryptedData();
588 doHandshake();
589 updateEventMask();
590 }
591
592
593
594
595 public synchronized boolean isInboundDone() {
596 return this.sslEngine.isInboundDone();
597 }
598
599
600
601
602 public synchronized boolean isOutboundDone() {
603 return this.sslEngine.isOutboundDone();
604 }
605
606 private synchronized int writePlain(final ByteBuffer src) throws IOException {
607 Args.notNull(src, "Byte buffer");
608 if (this.status != ACTIVE) {
609 throw new ClosedChannelException();
610 }
611 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
612 final SSLEngineResult result = doWrap(src, outEncryptedBuf);
613 if (result.getStatus() == Status.CLOSED) {
614 this.status = CLOSED;
615 }
616 return result.bytesConsumed();
617 }
618
619 private synchronized int readPlain(final ByteBuffer dst) {
620 Args.notNull(dst, "Byte buffer");
621 if (this.inPlain.hasData()) {
622
623 final ByteBuffer inPlainBuf = this.inPlain.acquire();
624
625
626 inPlainBuf.flip();
627 final int n = Math.min(inPlainBuf.remaining(), dst.remaining());
628 for (int i = 0; i < n; i++) {
629 dst.put(inPlainBuf.get());
630 }
631 inPlainBuf.compact();
632
633
634 if (inPlainBuf.position() == 0) {
635 this.inPlain.release();
636 }
637 return n;
638 }
639 return this.endOfStream ? -1 : 0;
640 }
641
642 @Override
643 public synchronized void close() {
644 if (this.status >= CLOSING) {
645 return;
646 }
647 this.status = CLOSING;
648 if (this.session.getSocketTimeout() == 0) {
649 this.session.setSocketTimeout(1000);
650 }
651 try {
652 updateEventMask();
653 } catch (final CancelledKeyException ex) {
654 shutdown();
655 }
656 }
657
658 @Override
659 public synchronized void shutdown() {
660 if (this.status == CLOSED) {
661 return;
662 }
663 this.status = CLOSED;
664 this.session.shutdown();
665
666 this.inEncrypted.release();
667 this.outEncrypted.release();
668 this.inPlain.release();
669
670 }
671
672 @Override
673 public int getStatus() {
674 return this.status;
675 }
676
677 @Override
678 public boolean isClosed() {
679 return this.status >= CLOSING || this.session.isClosed();
680 }
681
682 @Override
683 public ByteChannel channel() {
684 return this.channel;
685 }
686
687 @Override
688 public SocketAddress getLocalAddress() {
689 return this.session.getLocalAddress();
690 }
691
692 @Override
693 public SocketAddress getRemoteAddress() {
694 return this.session.getRemoteAddress();
695 }
696
697 @Override
698 public synchronized int getEventMask() {
699 return this.appEventMask;
700 }
701
702 @Override
703 public synchronized void setEventMask(final int ops) {
704 this.appEventMask = ops;
705 updateEventMask();
706 }
707
708 @Override
709 public synchronized void setEvent(final int op) {
710 this.appEventMask = this.appEventMask | op;
711 updateEventMask();
712 }
713
714 @Override
715 public synchronized void clearEvent(final int op) {
716 this.appEventMask = this.appEventMask & ~op;
717 updateEventMask();
718 }
719
720 @Override
721 public int getSocketTimeout() {
722 return this.session.getSocketTimeout();
723 }
724
725 @Override
726 public void setSocketTimeout(final int timeout) {
727 this.session.setSocketTimeout(timeout);
728 }
729
730 @Override
731 public synchronized boolean hasBufferedInput() {
732 return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedInput())
733 || this.inEncrypted.hasData()
734 || this.inPlain.hasData();
735 }
736
737 @Override
738 public synchronized boolean hasBufferedOutput() {
739 return (this.appBufferStatus != null && this.appBufferStatus.hasBufferedOutput())
740 || this.outEncrypted.hasData();
741 }
742
743 @Override
744 public synchronized void setBufferStatus(final SessionBufferStatus status) {
745 this.appBufferStatus = status;
746 }
747
748 @Override
749 public Object getAttribute(final String name) {
750 return this.session.getAttribute(name);
751 }
752
753 @Override
754 public Object removeAttribute(final String name) {
755 return this.session.removeAttribute(name);
756 }
757
758 @Override
759 public void setAttribute(final String name, final Object obj) {
760 this.session.setAttribute(name, obj);
761 }
762
763 private static void formatOps(final StringBuilder buffer, final int ops) {
764 if ((ops & SelectionKey.OP_READ) > 0) {
765 buffer.append('r');
766 }
767 if ((ops & SelectionKey.OP_WRITE) > 0) {
768 buffer.append('w');
769 }
770 }
771
772 @Override
773 public String toString() {
774 final StringBuilder buffer = new StringBuilder();
775 buffer.append(this.session);
776 buffer.append("[");
777 switch (this.status) {
778 case ACTIVE:
779 buffer.append("ACTIVE");
780 break;
781 case CLOSING:
782 buffer.append("CLOSING");
783 break;
784 case CLOSED:
785 buffer.append("CLOSED");
786 break;
787 }
788 buffer.append("][");
789 formatOps(buffer, this.appEventMask);
790 buffer.append("][");
791 buffer.append(this.sslEngine.getHandshakeStatus());
792 if (this.sslEngine.isInboundDone()) {
793 buffer.append("][inbound done][");
794 }
795 if (this.sslEngine.isOutboundDone()) {
796 buffer.append("][outbound done][");
797 }
798 if (this.endOfStream) {
799 buffer.append("][EOF][");
800 }
801 buffer.append("][");
802 buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position());
803 buffer.append("][");
804 buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position());
805 buffer.append("][");
806 buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position());
807 buffer.append("]");
808 return buffer.toString();
809 }
810
811 @Override
812 public Socket getSocket(){
813 return this.session instanceof SocketAccessor/../../../../../org/apache/http/nio/reactor/SocketAccessor.html#SocketAccessor">SocketAccessor ? ((SocketAccessor) this.session).getSocket() : null;
814 }
815
816 private class InternalByteChannel implements ByteChannel {
817
818 @Override
819 public int write(final ByteBuffer src) throws IOException {
820 return SSLIOSession.this.writePlain(src);
821 }
822
823 @Override
824 public int read(final ByteBuffer dst) throws IOException {
825 return SSLIOSession.this.readPlain(dst);
826 }
827
828 @Override
829 public void close() throws IOException {
830 SSLIOSession.this.close();
831 }
832
833 @Override
834 public boolean isOpen() {
835 return !SSLIOSession.this.isClosed();
836 }
837
838 }
839
840 }