001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2017-2019 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.net; 020 021import java.net.InetSocketAddress; 022import java.net.SocketAddress; 023import java.nio.ByteBuffer; 024import java.security.KeyManagementException; 025import java.security.NoSuchAlgorithmException; 026import java.security.cert.X509Certificate; 027import java.util.Collections; 028import java.util.List; 029import java.util.Optional; 030import java.util.concurrent.ExecutionException; 031import java.util.concurrent.FutureTask; 032import java.util.logging.Level; 033import javax.net.ssl.ExtendedSSLSession; 034import javax.net.ssl.SNIServerName; 035import javax.net.ssl.SSLContext; 036import javax.net.ssl.SSLEngine; 037import javax.net.ssl.SSLEngineResult; 038import javax.net.ssl.SSLEngineResult.HandshakeStatus; 039import javax.net.ssl.SSLEngineResult.Status; 040import javax.net.ssl.SSLException; 041import javax.net.ssl.TrustManager; 042import javax.net.ssl.X509TrustManager; 043import org.jgrapes.core.Channel; 044import org.jgrapes.core.ClassChannel; 045import org.jgrapes.core.Component; 046import org.jgrapes.core.Components; 047import org.jgrapes.core.EventPipeline; 048import org.jgrapes.core.annotation.Handler; 049import org.jgrapes.core.annotation.HandlerDefinition.ChannelReplacements; 050import org.jgrapes.io.IOSubchannel; 051import org.jgrapes.io.events.Close; 052import org.jgrapes.io.events.Closed; 053import org.jgrapes.io.events.ConnectError; 054import org.jgrapes.io.events.HalfClosed; 055import org.jgrapes.io.events.IOError; 056import org.jgrapes.io.events.Input; 057import org.jgrapes.io.events.OpenSocketConnection; 058import org.jgrapes.io.events.Output; 059import org.jgrapes.io.events.Purge; 060import org.jgrapes.io.util.LinkedIOSubchannel; 061import org.jgrapes.io.util.ManagedBuffer; 062import org.jgrapes.io.util.ManagedBufferPool; 063import org.jgrapes.net.events.Accepted; 064import org.jgrapes.net.events.ClientConnected; 065import org.jgrapes.net.events.Connected; 066 067/** 068 * A component that receives and sends byte buffers on an 069 * encrypted channel and sends and receives the corresponding 070 * decrypted data on a plain channel. 071 * 072 * The encrypted channel is assumed to be the network side 073 * ("upstream") and therefore {@link Input} events represent 074 * encrypted data and are decoded to {@link Output} events on 075 * the plain channel ("downstream") and vice versa. 076 */ 077@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.CouplingBetweenObjects" }) 078public class SslCodec extends Component { 079 080 private final Channel encryptedChannel; 081 private final SSLContext sslContext; 082 083 /** 084 * Represents the encrypted channel in annotations. 085 */ 086 private final class EncryptedChannel extends ClassChannel { 087 } 088 089 /** 090 * Creates a new codec that uses the given {@link SSLContext}. 091 * 092 * @param plainChannel the component's channel 093 * @param encryptedChannel the channel with the encrypted data 094 * @param sslContext the SSL context to use 095 */ 096 public SslCodec(Channel plainChannel, Channel encryptedChannel, 097 SSLContext sslContext) { 098 super(plainChannel, ChannelReplacements.create() 099 .add(EncryptedChannel.class, encryptedChannel)); 100 this.encryptedChannel = encryptedChannel; 101 this.sslContext = sslContext; 102 } 103 104 /** 105 * Creates a new codec to be used as client. 106 * 107 * @param plainChannel the component's channel 108 * @param encryptedChannel the channel with the encrypted data 109 * @param dontValidate if `true` accept all kinds of certificates 110 */ 111 @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.CommentRequired", 112 "PMD.ReturnEmptyArrayRatherThanNull", "PMD.UncommentedEmptyMethodBody", 113 "PMD.AvoidDuplicateLiterals" }) 114 public SslCodec(Channel plainChannel, Channel encryptedChannel, 115 boolean dontValidate) { 116 super(plainChannel, ChannelReplacements.create() 117 .add(EncryptedChannel.class, encryptedChannel)); 118 this.encryptedChannel = encryptedChannel; 119 try { 120 final SSLContext sslContext = SSLContext.getInstance("SSL"); 121 if (dontValidate) { 122 // Create a trust manager that does not validate certificate 123 // chains 124 final TrustManager[] trustAllCerts = { 125 new X509TrustManager() { 126 @Override 127 public X509Certificate[] getAcceptedIssuers() { 128 return new X509Certificate[0]; 129 } 130 131 @Override 132 public void checkClientTrusted( 133 X509Certificate[] certs, String authType) { 134 } 135 136 @Override 137 public void checkServerTrusted( 138 X509Certificate[] certs, String authType) { 139 } 140 } 141 }; 142 sslContext.init(null, trustAllCerts, null); 143 } else { 144 sslContext.init(null, null, null); 145 } 146 this.sslContext = sslContext; 147 } catch (NoSuchAlgorithmException | KeyManagementException e) { 148 throw new IllegalArgumentException(e); 149 } 150 } 151 152 /** 153 * Creates a new downstream connection as {@link LinkedIOSubchannel} 154 * of the network connection together with an {@link SSLEngine}. 155 * 156 * @param event 157 * the accepted event 158 */ 159 @Handler(channels = EncryptedChannel.class, excludeSelf = true) 160 public void onAccepted(Accepted event, IOSubchannel encryptedChannel) { 161 new PlainChannel(event, encryptedChannel); 162 } 163 164 /** 165 * Forward the connection request to the encrypted network. 166 * 167 * @param event the event 168 */ 169 @Handler 170 public void onOpenConnection(OpenSocketConnection event) { 171 fire(new OpenSocketConnection(event.address()) 172 .setAssociated(SslCodec.class, event), encryptedChannel); 173 } 174 175 /** 176 * Creates a new downstream connection as {@link LinkedIOSubchannel} 177 * of the network connection together with an {@link SSLEngine}. 178 * 179 * @param event 180 * the accepted event 181 */ 182 @Handler(channels = EncryptedChannel.class, excludeSelf = true) 183 public void onConnected(ClientConnected event, 184 IOSubchannel encryptedChannel) { 185 if (event.openEvent() 186 .associated(SslCodec.class, Object.class).isPresent()) { 187 new PlainChannel(event, encryptedChannel); 188 } 189 } 190 191 /** 192 * Handles encrypted data from upstream (the network). The data is 193 * send through the {@link SSLEngine} and events are sent downstream 194 * (and in the initial phases upstream) according to the conversion 195 * results. 196 * 197 * @param event the event 198 * @param encryptedChannel the channel for exchanging the encrypted data 199 * @throws InterruptedException 200 * @throws SSLException 201 * @throws ExecutionException 202 */ 203 @Handler(channels = EncryptedChannel.class, excludeSelf = true) 204 public void onInput(Input<ByteBuffer> event, IOSubchannel encryptedChannel) 205 throws InterruptedException, SSLException, ExecutionException { 206 @SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" }) 207 final Optional<PlainChannel> plainChannel 208 = (Optional<PlainChannel>) LinkedIOSubchannel 209 .downstreamChannel(this, encryptedChannel); 210 if (plainChannel.isPresent()) { 211 plainChannel.get().sendDownstream(event); 212 } 213 } 214 215 /** 216 * Handles a half close event from the encrypted channel (client). 217 * 218 * @param event the event 219 * @param encryptedChannel the channel for exchanging the encrypted data 220 * @throws InterruptedException 221 * @throws SSLException 222 */ 223 @Handler(channels = EncryptedChannel.class, excludeSelf = true) 224 public void onHalfClosed(HalfClosed event, IOSubchannel encryptedChannel) 225 throws SSLException, InterruptedException { 226 @SuppressWarnings("unchecked") 227 final Optional<PlainChannel> plainChannel 228 = (Optional<PlainChannel>) LinkedIOSubchannel 229 .downstreamChannel(this, encryptedChannel); 230 if (plainChannel.isPresent()) { 231 plainChannel.get().upstreamHalfClosed(); 232 } 233 } 234 235 /** 236 * Handles a close event from the encrypted channel (client). 237 * 238 * @param event the event 239 * @param encryptedChannel the channel for exchanging the encrypted data 240 * @throws InterruptedException 241 * @throws SSLException 242 */ 243 @Handler(channels = EncryptedChannel.class, excludeSelf = true) 244 public void onClosed(Closed<Void> event, IOSubchannel encryptedChannel) 245 throws SSLException, InterruptedException { 246 @SuppressWarnings("unchecked") 247 final Optional<PlainChannel> plainChannel 248 = (Optional<PlainChannel>) LinkedIOSubchannel 249 .downstreamChannel(this, encryptedChannel); 250 if (plainChannel.isPresent()) { 251 plainChannel.get().upstreamClosed(); 252 } 253 } 254 255 /** 256 * Forwards a {@link Purge} event downstream. 257 * 258 * @param event the event 259 * @param encryptedChannel the encrypted channel 260 */ 261 @Handler(channels = EncryptedChannel.class, excludeSelf = true) 262 public void onPurge(Purge event, IOSubchannel encryptedChannel) { 263 @SuppressWarnings("unchecked") 264 final Optional<PlainChannel> plainChannel 265 = (Optional<PlainChannel>) LinkedIOSubchannel 266 .downstreamChannel(this, encryptedChannel); 267 if (plainChannel.isPresent()) { 268 plainChannel.get().purge(); 269 } 270 } 271 272 /** 273 * Handles an {@link IOError} event from the encrypted channel (client) 274 * by sending it downstream. 275 * 276 * @param event the event 277 * @throws SSLException the SSL exception 278 * @throws InterruptedException the interrupted exception 279 */ 280 @Handler(channels = EncryptedChannel.class, excludeSelf = true) 281 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 282 public void onIOError(IOError event) 283 throws SSLException, InterruptedException { 284 for (Channel channel : event.channels()) { 285 if (channel instanceof SocketIOChannel netConnChannel) { 286 @SuppressWarnings("unchecked") 287 final Optional<PlainChannel> plainChannel 288 = (Optional<PlainChannel>) LinkedIOSubchannel 289 .downstreamChannel(this, netConnChannel); 290 plainChannel.ifPresent(c -> fire(IOError.duplicate(event), c)); 291 continue; 292 } 293 294 // Error while trying to establish the network connection 295 if (event.event() instanceof OpenSocketConnection openEvent) { 296 openEvent.associated(SslCodec.class, OpenSocketConnection.class) 297 .ifPresent( 298 e -> fire(new ConnectError(e, event.message()), this)); 299 } 300 } 301 } 302 303 /** 304 * Sends plain data through the engine and then upstream. 305 * 306 * @param event 307 * the event with the data 308 * @throws InterruptedException if the execution was interrupted 309 * @throws SSLException if some SSL related problem occurs 310 * @throws ExecutionException 311 */ 312 @Handler 313 public void onOutput(Output<ByteBuffer> event, PlainChannel plainChannel) 314 throws InterruptedException, SSLException, ExecutionException { 315 if (plainChannel.hub() != this) { 316 return; 317 } 318 plainChannel.sendUpstream(event); 319 } 320 321 /** 322 * Forwards a close event upstream. 323 * 324 * @param event 325 * the close event 326 * @throws SSLException if an SSL related problem occurs 327 * @throws InterruptedException if the execution was interrupted 328 */ 329 @Handler 330 public void onClose(Close event, PlainChannel plainChannel) 331 throws InterruptedException, SSLException { 332 if (plainChannel.hub() != this) { 333 return; 334 } 335 plainChannel.close(event); 336 } 337 338 /** 339 * Represents the plain channel. 340 */ 341 @SuppressWarnings("PMD.DataflowAnomalyAnalysis") 342 private class PlainChannel extends LinkedIOSubchannel 343 implements SocketIOChannel { 344 public final SocketAddress localAddress; 345 public final SocketAddress remoteAddress; 346 public SSLEngine sslEngine; 347 private EventPipeline downPipeline; 348 private ManagedBufferPool<ManagedBuffer<ByteBuffer>, 349 ByteBuffer> downstreamPool; 350 private ByteBuffer carryOver; 351 private final boolean[] inputProcessed = { false }; 352 353 /** 354 * Instantiates a new plain channel from an accepted connection. 355 * 356 * @param event the event 357 * @param upstreamChannel the upstream channel 358 */ 359 public PlainChannel(Accepted event, IOSubchannel upstreamChannel) { 360 super(SslCodec.this, channel(), upstreamChannel, 361 newEventPipeline()); 362 localAddress = event.localAddress(); 363 remoteAddress = event.remoteAddress(); 364 init(); 365 sslEngine.setUseClientMode(false); 366 } 367 368 /** 369 * Instantiates a new plain channel from an established connection. 370 * 371 * @param event the event 372 * @param upstreamChannel the upstream channel 373 */ 374 public PlainChannel(Connected<?> event, IOSubchannel upstreamChannel) { 375 super(SslCodec.this, channel(), upstreamChannel, 376 newEventPipeline()); 377 localAddress = event.localAddress(); 378 remoteAddress = event.remoteAddress(); 379 init(); 380 sslEngine.setUseClientMode(true); 381 382 // Forward downstream 383 if (event instanceof ClientConnected) { 384 downPipeline.fire(new ClientConnected( 385 ((ClientConnected) event).openEvent().associated( 386 SslCodec.class, OpenSocketConnection.class).get(), 387 event.localAddress(), event.remoteAddress()), this); 388 389 } else { 390 downPipeline.fire(new Connected<Void>( 391 event.localAddress(), event.remoteAddress()), this); 392 } 393 } 394 395 private void init() { 396 if (remoteAddress instanceof InetSocketAddress) { 397 sslEngine = sslContext.createSSLEngine( 398 ((InetSocketAddress) remoteAddress).getAddress() 399 .getHostAddress(), 400 ((InetSocketAddress) remoteAddress).getPort()); 401 } else { 402 sslEngine = sslContext.createSSLEngine(); 403 } 404 String channelName = Components.objectName(SslCodec.this) 405 + "." + Components.objectName(this); 406 // Create buffer pools, adding 50 to decoded application buffer 407 // size, see 408 // https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/samples/sslengine/SSLEngineSimpleDemo.java 409 final int appBufSize 410 = sslEngine.getSession().getApplicationBufferSize(); 411 downstreamPool = new ManagedBufferPool<>(ManagedBuffer::new, 412 () -> ByteBuffer.allocate(appBufSize + 50), 2) 413 .setName(channelName + ".downstream.buffers"); 414 // Provide buffers with application buffer size 415 // for use by downstream components. 416 setByteBufferPool(new ManagedBufferPool<>(ManagedBuffer::new, 417 () -> ByteBuffer.allocate(appBufSize), 2) 418 .setName(channelName + ".upstream.buffers")); 419 downPipeline = newEventPipeline(); 420 // Buffers for sending encrypted data upstream will be 421 // obtained from upstream() and resized if required. 422 } 423 424 /** 425 * Sends input downstream. 426 * 427 * @param event the event 428 * @throws SSLException the SSL exception 429 * @throws InterruptedException the interrupted exception 430 * @throws ExecutionException the execution exception 431 */ 432 public void sendDownstream(Input<ByteBuffer> event) 433 throws SSLException, InterruptedException, ExecutionException { 434 ByteBuffer input = event.buffer().duplicate(); 435 if (carryOver != null) { 436 if (carryOver.remaining() < input.remaining()) { 437 // Shouldn't happen with carryOver having packet size 438 // bytes left, have seen it happen nevertheless. 439 carryOver.flip(); 440 ByteBuffer extCarryOver = ByteBuffer.allocate( 441 carryOver.remaining() + input.remaining()); 442 extCarryOver.put(carryOver); 443 carryOver = extCarryOver; 444 } 445 carryOver.put(input); 446 carryOver.flip(); 447 input = carryOver; 448 carryOver = null; 449 } 450 451 // Main processing 452 processInput(input); 453 454 // Check if data from incomplete packet remains in input buffer 455 if (input.hasRemaining()) { 456 // Actually, packet buffer size should be sufficient, 457 // but since this is hard to test and doesn't really matter... 458 carryOver = ByteBuffer.allocate(input.remaining() 459 + sslEngine.getSession().getPacketBufferSize() + 50); 460 carryOver.put(input); 461 } 462 } 463 464 @SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NcssCount", 465 "PMD.AvoidInstantiatingObjectsInLoops", "PMD.ExcessiveMethodLength", 466 "PMD.NPathComplexity", "PMD.CognitiveComplexity" }) 467 private SSLEngineResult processInput(ByteBuffer input) 468 throws SSLException, InterruptedException, ExecutionException { 469 SSLEngineResult unwrapResult; 470 ManagedBuffer<ByteBuffer> unwrapped = downstreamPool.acquire(); 471 while (true) { 472 unwrapResult 473 = sslEngine.unwrap(input, unwrapped.backingBuffer()); 474 synchronized (inputProcessed) { 475 inputProcessed[0] = true; 476 inputProcessed.notifyAll(); 477 } 478 // Handle any handshaking procedures 479 switch (unwrapResult.getHandshakeStatus()) { 480 case NEED_TASK: 481 while (true) { 482 Runnable runnable = sslEngine.getDelegatedTask(); 483 if (runnable == null) { 484 break; 485 } 486 // Having this handled by the response thread is 487 // probably not really necessary, but as the delegated 488 // task usually includes sending upstream... 489 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 490 FutureTask<Boolean> task 491 = new FutureTask<>(runnable, true); 492 upstreamChannel().responsePipeline() 493 .executorService().submit(task); 494 task.get(); 495 } 496 continue; 497 498 case NEED_WRAP: 499 ManagedBuffer<ByteBuffer> feedback 500 = acquireUpstreamBuffer(); 501 synchronized (sslEngine) { 502 SSLEngineResult wrapResult = sslEngine.wrap( 503 ManagedBuffer.EMPTY_BYTE_BUFFER.backingBuffer(), 504 feedback.backingBuffer()); 505 // JDK11 sometimes returns NEED_WRAP (together 506 // with status is CLOSED) but does not produce 507 // anything when wrapping (all of which does 508 // not make sense). 509 if (feedback.position() == 0 510 && unwrapResult.getStatus() == Status.CLOSED) { 511 feedback.unlockBuffer(); 512 break; 513 } 514 upstreamChannel() 515 .respond(Output.fromSink(feedback, false)); 516 if (wrapResult 517 .getHandshakeStatus() == HandshakeStatus.FINISHED) { 518 fireAccepted(); 519 } 520 } 521 continue; 522 523 case FINISHED: 524 fireAccepted(); 525 break; 526 527 default: 528 break; 529 } 530 531 // Anything to forward downstream? 532 if (unwrapped.position() > 0) { 533 // forward unwrapped data 534 downPipeline.fire(Input.fromSink(unwrapped, 535 sslEngine.isInboundDone()), this); 536 unwrapped = null; 537 } 538 539 // If we have a buffer overflow or everything was okay 540 // and there's data left, we try again, else we quit. 541 if (unwrapResult.getStatus() != Status.BUFFER_OVERFLOW 542 && (unwrapResult.getStatus() != Status.OK 543 || !input.hasRemaining())) { 544 // Underflow or closed 545 if (unwrapped != null) { 546 unwrapped.unlockBuffer(); 547 } 548 break; 549 } 550 551 // Make sure we have an output buffer. 552 if (unwrapped == null) { 553 unwrapped = downstreamPool.acquire(); 554 } 555 } 556 return unwrapResult; 557 } 558 559 @SuppressWarnings("PMD.DataflowAnomalyAnalysis") 560 private void fireAccepted() { 561 List<SNIServerName> snis = Collections.emptyList(); 562 if (sslEngine.getSession() instanceof ExtendedSSLSession) { 563 snis = ((ExtendedSSLSession) sslEngine.getSession()) 564 .getRequestedServerNames(); 565 } 566 downPipeline.fire(new Accepted( 567 localAddress, remoteAddress, true, snis), this); 568 } 569 570 /** 571 * Send output upstream. 572 * 573 * @param event the event 574 * @throws SSLException the SSL exception 575 * @throws InterruptedException the interrupted exception 576 * @throws ExecutionException 577 */ 578 public void sendUpstream(Output<ByteBuffer> event) 579 throws SSLException, InterruptedException, ExecutionException { 580 ByteBuffer output = event.buffer().backingBuffer().duplicate(); 581 processOutput(output, event.isEndOfRecord()); 582 } 583 584 @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.NcssCount", 585 "PMD.CyclomaticComplexity", "PMD.NPathComplexity", 586 "PMD.CognitiveComplexity" }) 587 private void processOutput(ByteBuffer output, boolean eor) 588 throws InterruptedException, SSLException, ExecutionException { 589 ManagedBuffer<ByteBuffer> wrapped = acquireUpstreamBuffer(); 590 while (true) { 591 SSLEngineResult wrapResult; 592 // Cheap synchronization: no (relevant) input 593 inputProcessed[0] = false; 594 synchronized (sslEngine) { 595 wrapResult 596 = sslEngine.wrap(output, wrapped.backingBuffer()); 597 // Anything to be sent upstream? 598 if (wrapped.position() > 0) { 599 upstreamChannel().respond(Output.fromSink(wrapped, 600 sslEngine.isInboundDone() 601 || eor && !output.hasRemaining())); 602 wrapped = null; 603 } 604 } 605 switch (wrapResult.getHandshakeStatus()) { 606 case NEED_TASK: 607 while (true) { 608 Runnable runnable = sslEngine.getDelegatedTask(); 609 if (runnable == null) { 610 break; 611 } 612 runnable.run(); 613 } 614 continue; 615 616 case NEED_UNWRAP: 617 // Input required. Wait until 618 // input becomes available and retry. 619 synchronized (inputProcessed) { 620 while (!inputProcessed[0]) { 621 inputProcessed.wait(); 622 } 623 } 624 break; 625 626 default: 627 break; 628 } 629 630 // If we have a buffer overflow or everything was okay 631 // and there's data left, we try again, else we quit. 632 if (wrapResult.getStatus() != Status.BUFFER_OVERFLOW 633 && (wrapResult.getStatus() != Status.OK 634 || !output.hasRemaining())) { 635 // Underflow or closed 636 if (wrapped != null) { 637 wrapped.unlockBuffer(); 638 } 639 // Warn if data is discarded 640 if (output.hasRemaining()) { 641 logger.warning(() -> toString() 642 + ": Upstream data discarded, SSLEngine status: " 643 + wrapResult.getStatus()); 644 } 645 break; 646 } 647 648 // Was handshake (or partial content), get new buffer and try 649 // again 650 if (wrapped == null) { 651 wrapped = acquireUpstreamBuffer(); 652 } 653 } 654 } 655 656 /** 657 * Close the connection. 658 * 659 * @param event the event 660 * @throws InterruptedException the interrupted exception 661 * @throws SSLException the SSL exception 662 */ 663 public void close(Close event) 664 throws InterruptedException, SSLException { 665 sslEngine.closeOutbound(); 666 while (!sslEngine.isOutboundDone()) { 667 ManagedBuffer<ByteBuffer> feedback = acquireUpstreamBuffer(); 668 sslEngine.wrap(ManagedBuffer.EMPTY_BYTE_BUFFER 669 .backingBuffer(), feedback.backingBuffer()); 670 upstreamChannel().respond(Output.fromSink(feedback, false)); 671 } 672 upstreamChannel().respond(new Close()); 673 } 674 675 /** 676 * Handles the {@link HalfClosed} event. 677 * 678 * @throws SSLException the SSL exception 679 * @throws InterruptedException the interrupted exception 680 */ 681 @SuppressWarnings("PMD.GuardLogStatement") 682 public void upstreamHalfClosed() 683 throws SSLException, InterruptedException { 684 if (sslEngine.isInboundDone()) { 685 // Was properly closed on SSL level 686 return; 687 } 688 try { 689 sslEngine.closeInbound(); 690 sslEngine.closeOutbound(); 691 while (!sslEngine.isOutboundDone()) { 692 ManagedBuffer<ByteBuffer> feedback 693 = acquireUpstreamBuffer(); 694 SSLEngineResult result = sslEngine.wrap( 695 ManagedBuffer.EMPTY_BYTE_BUFFER.backingBuffer(), 696 feedback.backingBuffer()); 697 // This is required for/since JDK 11. It claims that 698 // outbound is not done, but doesn't produce any 699 // additional 700 // data. 701 if (result.getStatus() == Status.CLOSED 702 || feedback.position() == 0) { 703 feedback.unlockBuffer(); 704 break; 705 } 706 upstreamChannel() 707 .respond(Output.fromSink(feedback, false)); 708 } 709 } catch (SSLException e) { 710 // Several clients (notably chromium, see 711 // https://bugs.chromium.org/p/chromium/issues/detail?id=118366 712 // don't close the connection properly. So nobody is really 713 // interested in this message 714 logger.log(Level.FINEST, e.getMessage(), e); 715 } catch (InterruptedException e) { 716 logger.log(Level.WARNING, e.getMessage(), e); 717 } 718 } 719 720 /** 721 * Forwards the {@link Closed} event downstream. 722 * 723 * @throws SSLException the SSL exception 724 * @throws InterruptedException the interrupted exception 725 */ 726 public void upstreamClosed() 727 throws SSLException, InterruptedException { 728 downPipeline.fire(new Closed<Void>(), this); 729 } 730 731 private ManagedBuffer<ByteBuffer> acquireUpstreamBuffer() 732 throws InterruptedException { 733 ManagedBuffer<ByteBuffer> feedback 734 = upstreamChannel().byteBufferPool().acquire(); 735 int encSize 736 = sslEngine.getSession().getPacketBufferSize() + 50; 737 if (feedback.capacity() < encSize) { 738 feedback.replaceBackingBuffer(ByteBuffer.allocate( 739 encSize)); 740 } 741 return feedback; 742 } 743 744 /** 745 * Fire a {@link Purge} event downstream. 746 */ 747 public void purge() { 748 downPipeline.fire(new Purge(), this); 749 } 750 751 @Override 752 public SocketAddress localAddress() { 753 return localAddress; 754 } 755 756 @Override 757 public SocketAddress remoteAddress() { 758 return remoteAddress; 759 } 760 761 @Override 762 public boolean isPurgeable() { 763 return false; 764 } 765 766 @Override 767 public long purgeableSince() { 768 return 0; 769 } 770 771 } 772}