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