SslHandler#wrap to preserve exception if SSLEngine is closed (#10327)
Motivation: SslHandler currently throws a general SSLException if a wrap attempt fails due to the SSLEngine being closed. If writes are queued the failure rational typically requires more investigation to track down the original failure from a previous event. We may have more informative rational for the failure and so we should use it. Modifications: - SslHandler#wrap to use failure information from the handshake or prior transport closure if available Result: More informative exceptions from SslHandler#wrap if the SSLEngine has been previously closed.
This commit is contained in:
parent
689414ff0f
commit
32a77394a3
@ -847,7 +847,15 @@ public class SslHandler extends ByteToMessageDecoder {
|
||||
if (result.getStatus() == Status.CLOSED) {
|
||||
buf.release();
|
||||
buf = null;
|
||||
SSLException exception = new SSLException("SSLEngine closed already");
|
||||
// Make a best effort to preserve any exception that way previously encountered from the handshake
|
||||
// or the transport, else fallback to a general error.
|
||||
Throwable exception = handshakePromise.cause();
|
||||
if (exception == null) {
|
||||
exception = sslClosePromise.cause();
|
||||
if (exception == null) {
|
||||
exception = new SSLException("SSLEngine closed already");
|
||||
}
|
||||
}
|
||||
promise.tryFailure(exception);
|
||||
promise = null;
|
||||
// SSLEngine has been closed already.
|
||||
|
@ -41,6 +41,8 @@ import io.netty.handler.ssl.util.SimpleTrustManagerFactory;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.NetUtil;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.util.concurrent.UnaryPromiseNotifier;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import io.netty.util.internal.EmptyArrays;
|
||||
@ -844,21 +846,27 @@ public abstract class SSLEngineTest {
|
||||
|
||||
@Test
|
||||
public void testClientHostnameValidationFail() throws InterruptedException, SSLException {
|
||||
mySetupClientHostnameValidation(ResourcesUtil.getFile(getClass(), "notlocalhost_server.pem"),
|
||||
ResourcesUtil.getFile(getClass(), "notlocalhost_server.key"),
|
||||
ResourcesUtil.getFile(getClass(), "mutual_auth_ca.pem"),
|
||||
true);
|
||||
Future<Void> clientWriteFuture =
|
||||
mySetupClientHostnameValidation(ResourcesUtil.getFile(getClass(), "notlocalhost_server.pem"),
|
||||
ResourcesUtil.getFile(getClass(), "notlocalhost_server.key"),
|
||||
ResourcesUtil.getFile(getClass(), "mutual_auth_ca.pem"),
|
||||
true);
|
||||
assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue("unexpected exception: " + clientException,
|
||||
mySetupMutualAuthServerIsValidClientException(clientException));
|
||||
assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue("unexpected exception: " + serverException,
|
||||
mySetupMutualAuthServerIsValidServerException(serverException));
|
||||
|
||||
// Verify that any pending writes are failed with the cached handshake exception and not a general SSLException.
|
||||
clientWriteFuture.awaitUninterruptibly();
|
||||
Throwable actualCause = clientWriteFuture.cause();
|
||||
assertSame(clientException, actualCause);
|
||||
}
|
||||
|
||||
private void mySetupClientHostnameValidation(File serverCrtFile, File serverKeyFile,
|
||||
File clientTrustCrtFile,
|
||||
final boolean failureExpected)
|
||||
private Future<Void> mySetupClientHostnameValidation(File serverCrtFile, File serverKeyFile,
|
||||
File clientTrustCrtFile,
|
||||
final boolean failureExpected)
|
||||
throws SSLException, InterruptedException {
|
||||
final String expectedHost = "localhost";
|
||||
serverSslCtx = wrapContext(SslContextBuilder.forServer(serverCrtFile, serverKeyFile, null)
|
||||
@ -930,6 +938,7 @@ public abstract class SSLEngineTest {
|
||||
}
|
||||
});
|
||||
|
||||
final Promise<Void> clientWritePromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
cb.group(new MultithreadEventLoopGroup(NioHandler.newFactory()));
|
||||
cb.channel(NioSocketChannel.class);
|
||||
cb.handler(new ChannelInitializer<Channel>() {
|
||||
@ -953,6 +962,17 @@ public abstract class SSLEngineTest {
|
||||
p.addLast(sslHandler);
|
||||
p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch));
|
||||
p.addLast(new ChannelHandler() {
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||
// Only write if there is a failure expected. We don't actually care about the write going
|
||||
// through we just want to verify the local failure condition. This way we don't have to worry
|
||||
// about verifying the payload and releasing the content on the server side.
|
||||
if (failureExpected) {
|
||||
ctx.write(ctx.alloc().buffer(1).writeByte(1))
|
||||
.addListener(new UnaryPromiseNotifier<Void>(clientWritePromise));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt == SslHandshakeCompletionEvent.SUCCESS) {
|
||||
@ -986,6 +1006,7 @@ public abstract class SSLEngineTest {
|
||||
ChannelFuture ccf = cb.connect(new InetSocketAddress(expectedHost, port));
|
||||
assertTrue(ccf.awaitUninterruptibly().isSuccess());
|
||||
clientChannel = ccf.channel();
|
||||
return clientWritePromise;
|
||||
}
|
||||
|
||||
private void mySetupMutualAuth(File keyFile, File crtFile, String keyPassword)
|
||||
|
Loading…
x
Reference in New Issue
Block a user