/*
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.CodecException;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.ssl.util.SimpleTrustManagerFactory;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.PromiseNotifier;
import io.netty.util.internal.EmptyArrays;
import org.junit.Test;
import java.io.File;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
public class SslHandlerTest {
@Test
public void testTruncatedPacket() throws Exception {
SSLEngine engine = SSLContext.getDefault().createSSLEngine();
engine.setUseClientMode(false);
EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(engine));
// Push the first part of a 5-byte handshake message.
ch.writeInbound(Unpooled.wrappedBuffer(new byte[]{22, 3, 1, 0, 5}));
// Should decode nothing yet.
assertThat(ch.readInbound(), is(nullValue()));
try {
// Push the second part of the 5-byte handshake message.
ch.writeInbound(Unpooled.wrappedBuffer(new byte[]{2, 0, 0, 1, 0}));
fail();
} catch (DecoderException e) {
// Be sure we cleanup the channel and release any pending messages that may have been generated because
// of an alert.
// See https://github.com/netty/netty/issues/6057.
ch.finishAndReleaseAll();
// The pushed message is invalid, so it should raise an exception if it decoded the message correctly.
assertThat(e.getCause(), is(instanceOf(SSLProtocolException.class)));
}
}
@Test(expected = UnsupportedMessageTypeException.class)
public void testNonByteBufNotPassThrough() throws Exception {
SSLEngine engine = SSLContext.getDefault().createSSLEngine();
engine.setUseClientMode(false);
EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(engine));
try {
ch.writeOutbound(new Object());
} finally {
ch.finishAndReleaseAll();
}
}
@Test
public void testReleaseSslEngine() throws Exception {
assumeTrue(OpenSsl.isAvailable());
SelfSignedCertificate cert = new SelfSignedCertificate();
try {
SslContext sslContext = SslContextBuilder.forServer(cert.certificate(), cert.privateKey())
.sslProvider(SslProvider.OPENSSL)
.build();
try {
SSLEngine sslEngine = sslContext.newEngine(ByteBufAllocator.DEFAULT);
EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(sslEngine));
assertEquals(1, ((ReferenceCounted) sslContext).refCnt());
assertEquals(1, ((ReferenceCounted) sslEngine).refCnt());
assertTrue(ch.finishAndReleaseAll());
ch.close().syncUninterruptibly();
assertEquals(1, ((ReferenceCounted) sslContext).refCnt());
assertEquals(0, ((ReferenceCounted) sslEngine).refCnt());
} finally {
ReferenceCountUtil.release(sslContext);
}
} finally {
cert.delete();
}
}
private static final class TlsReadTest extends ChannelOutboundHandlerAdapter {
private volatile boolean readIssued;
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
readIssued = true;
super.read(ctx);
}
public void test(final boolean dropChannelActive) throws Exception {
SSLEngine engine = SSLContext.getDefault().createSSLEngine();
engine.setUseClientMode(true);
EmbeddedChannel ch = new EmbeddedChannel(
this,
new SslHandler(engine),
new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (!dropChannelActive) {
ctx.fireChannelActive();
}
}
}
);
ch.config().setAutoRead(false);
assertFalse(ch.config().isAutoRead());
assertTrue(ch.writeOutbound(Unpooled.EMPTY_BUFFER));
assertTrue(readIssued);
assertTrue(ch.finishAndReleaseAll());
}
}
@Test
public void testIssueReadAfterActiveWriteFlush() throws Exception {
// the handshake is initiated by channelActive
new TlsReadTest().test(false);
}
@Test
public void testIssueReadAfterWriteFlushActive() throws Exception {
// the handshake is initiated by flush
new TlsReadTest().test(true);
}
@Test(timeout = 30000)
public void testRemoval() throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup();
Channel sc = null;
Channel cc = null;
try {
final Promise<Void> clientPromise = group.next().newPromise();
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(newHandler(SslContextBuilder.forClient().trustManager(
InsecureTrustManagerFactory.INSTANCE).build(), clientPromise));
SelfSignedCertificate ssc = new SelfSignedCertificate();
final Promise<Void> serverPromise = group.next().newPromise();
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(group, group)
.channel(NioServerSocketChannel.class)
.childHandler(newHandler(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(),
serverPromise));
sc = serverBootstrap.bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
cc = bootstrap.connect(sc.localAddress()).syncUninterruptibly().channel();
serverPromise.syncUninterruptibly();
clientPromise.syncUninterruptibly();
} finally {
if (cc != null) {
cc.close().syncUninterruptibly();
}
if (sc != null) {
sc.close().syncUninterruptibly();
}
group.shutdownGracefully();
}
}
private static ChannelHandler newHandler(final SslContext sslCtx, final Promise<Void> promise) {
return new ChannelInitializer() {
@Override
protected void initChannel(final Channel ch) {
final SslHandler sslHandler = sslCtx.newHandler(ch.alloc());
sslHandler.setHandshakeTimeoutMillis(1000);
ch.pipeline().addFirst(sslHandler);
sslHandler.handshakeFuture().addListener(new FutureListener<Channel>() {
@Override
public void operationComplete(final Future<Channel> future) {
ch.pipeline().remove(sslHandler);
// Schedule the close so removal has time to propagate exception if any.
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
ch.close();
}
});
}
});
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (cause instanceof CodecException) {
cause = cause.getCause();
}
if (cause instanceof IllegalReferenceCountException) {
promise.setFailure(cause);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
promise.trySuccess(null);
}
});
}
};
}
@Test(timeout = 30000)
public void testAlertProducedAndSendJdk() throws Exception {
testAlertProducedAndSend(SslProvider.JDK);
}
@Test(timeout = 30000)
public void testAlertProducedAndSendOpenSsl() throws Exception {
assumeTrue(OpenSsl.isAvailable());
testAlertProducedAndSend(SslProvider.OPENSSL);
testAlertProducedAndSend(SslProvider.OPENSSL_REFCNT);
}
private void testAlertProducedAndSend(SslProvider provider) throws Exception {
SelfSignedCertificate ssc = new SelfSignedCertificate();
final SslContext sslServerCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(provider)
.trustManager(new SimpleTrustManagerFactory() {
@Override
protected void engineInit(KeyStore keyStore) { }
@Override
protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { }
@Override
protected TrustManager[] engineGetTrustManagers() {
return new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
throws CertificateException {
// Fail verification which should produce an alert that is send back to the client.
throw new CertificateException();
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
// NOOP
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return EmptyArrays.EMPTY_X509_CERTIFICATES;
}
} };
}
}).clientAuth(ClientAuth.REQUIRE).build();
final SslContext sslClientCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.keyManager(new File(getClass().getResource("test.crt").getFile()),
new File(getClass().getResource("test_unencrypted.pem").getFile()))
.sslProvider(provider).build();
NioEventLoopGroup group = new NioEventLoopGroup();
Channel sc = null;
Channel cc = null;
try {
final Promise<Void> promise = group.next().newPromise();
sc = new ServerBootstrap()
.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(sslServerCtx.newHandler(ch.alloc()));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Just trigger a close
ctx.close();
}
});
}
}).bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
cc = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(sslClientCtx.newHandler(ch.alloc()));
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (cause.getCause() instanceof SSLException) {
// We received the alert and so produce an SSLException.
promise.setSuccess(null);
}
}
});
}
}).connect(sc.localAddress()).syncUninterruptibly().channel();
promise.syncUninterruptibly();
} finally {
if (cc != null) {
cc.close().syncUninterruptibly();
}
if (sc != null) {
sc.close().syncUninterruptibly();
}
group.shutdownGracefully();
ReferenceCountUtil.release(sslServerCtx);
ReferenceCountUtil.release(sslClientCtx);
}
}
@Test
public void testCloseFutureNotified() throws Exception {
SslHandler handler = new SslHandler(SSLContext.getDefault().createSSLEngine());
EmbeddedChannel ch = new EmbeddedChannel(handler);
// Closing the Channel will also produce a close_notify so it is expected to return true.
assertTrue(ch.finishAndReleaseAll());
assertTrue(handler.handshakeFuture().cause() instanceof ClosedChannelException);
assertTrue(handler.sslCloseFuture().cause() instanceof ClosedChannelException);
}
@Test(timeout = 5000)
public void testEventsFired() throws Exception {
SSLEngine engine = SSLContext.getDefault().createSSLEngine();
final BlockingQueue<SslCompletionEvent> events = new LinkedBlockingQueue<SslCompletionEvent>();
EmbeddedChannel channel = new EmbeddedChannel(new SslHandler(engine), new ChannelInboundHandlerAdapter() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof SslCompletionEvent) {
events.add((SslCompletionEvent) evt);
}
}
});
assertTrue(events.isEmpty());
assertTrue(channel.finishAndReleaseAll());
SslCompletionEvent evt = events.take();
assertTrue(evt instanceof SslHandshakeCompletionEvent);
assertTrue(evt.cause() instanceof ClosedChannelException);
evt = events.take();
assertTrue(evt instanceof SslCloseCompletionEvent);
assertTrue(evt.cause() instanceof ClosedChannelException);
assertTrue(events.isEmpty());
}
@Test(timeout = 30000)
public void testCloseNotifyReceivedJdk() throws Exception {
testCloseNotify(SslProvider.JDK, 5000, false);
}
@Test(timeout = 30000)
public void testCloseNotifyReceivedOpenSsl() throws Exception {
assumeTrue(OpenSsl.isAvailable());
testCloseNotify(SslProvider.OPENSSL, 5000, false);
testCloseNotify(SslProvider.OPENSSL_REFCNT, 5000, false);
}
@Test(timeout = 30000)
public void testCloseNotifyReceivedJdkTimeout() throws Exception {
testCloseNotify(SslProvider.JDK, 100, true);
}
@Test(timeout = 30000)
public void testCloseNotifyReceivedOpenSslTimeout() throws Exception {
assumeTrue(OpenSsl.isAvailable());
testCloseNotify(SslProvider.OPENSSL, 100, true);
testCloseNotify(SslProvider.OPENSSL_REFCNT, 100, true);
}
@Test(timeout = 30000)
public void testCloseNotifyNotWaitForResponseJdk() throws Exception {
testCloseNotify(SslProvider.JDK, 0, false);
}
@Test(timeout = 30000)
public void testCloseNotifyNotWaitForResponseOpenSsl() throws Exception {
assumeTrue(OpenSsl.isAvailable());
testCloseNotify(SslProvider.OPENSSL, 0, false);
testCloseNotify(SslProvider.OPENSSL_REFCNT, 0, false);
}
private static void testCloseNotify(SslProvider provider, final long closeNotifyReadTimeout, final boolean timeout)
throws Exception {
SelfSignedCertificate ssc = new SelfSignedCertificate();
final SslContext sslServerCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(provider)
.build();
final SslContext sslClientCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(provider).build();
EventLoopGroup group = new NioEventLoopGroup();
Channel sc = null;
Channel cc = null;
try {
final Promise<Channel> clientPromise = group.next().newPromise();
final Promise<Channel> serverPromise = group.next().newPromise();
sc = new ServerBootstrap()
.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
SslHandler handler = sslServerCtx.newHandler(ch.alloc());
handler.setCloseNotifyReadTimeoutMillis(closeNotifyReadTimeout);
handler.sslCloseFuture().addListener(
new PromiseNotifier<Channel, Future<Channel>>(serverPromise));
handler.handshakeFuture().addListener(new FutureListener<Channel>() {
@Override
public void operationComplete(Future<Channel> future) {
if (!future.isSuccess()) {
// Something bad happened during handshake fail the promise!
serverPromise.tryFailure(future.cause());
}
}
});
ch.pipeline().addLast(handler);
}
}).bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
cc = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
final AtomicBoolean closeSent = new AtomicBoolean();
if (timeout) {
ch.pipeline().addFirst(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (closeSent.get()) {
// Drop data on the floor so we will get a timeout while waiting for the
// close_notify.
ReferenceCountUtil.release(msg);
} else {
super.channelRead(ctx, msg);
}
}
});
}
SslHandler handler = sslClientCtx.newHandler(ch.alloc());
handler.setCloseNotifyReadTimeoutMillis(closeNotifyReadTimeout);
handler.sslCloseFuture().addListener(
new PromiseNotifier<Channel, Future<Channel>>(clientPromise));
handler.handshakeFuture().addListener(new FutureListener<Channel>() {
@Override
public void operationComplete(Future<Channel> future) {
if (future.isSuccess()) {
closeSent.compareAndSet(false, true);
future.getNow().close();
} else {
// Something bad happened during handshake fail the promise!
clientPromise.tryFailure(future.cause());
}
}
});
ch.pipeline().addLast(handler);
}
}).connect(sc.localAddress()).syncUninterruptibly().channel();
serverPromise.awaitUninterruptibly();
clientPromise.awaitUninterruptibly();
// Server always received the close_notify as the client triggers the close sequence.
assertTrue(serverPromise.isSuccess());
// Depending on if we wait for the response or not the promise will be failed or not.
if (closeNotifyReadTimeout > 0 && !timeout) {
assertTrue(clientPromise.isSuccess());
} else {
assertFalse(clientPromise.isSuccess());
}
} finally {
if (cc != null) {
cc.close().syncUninterruptibly();
}
if (sc != null) {
sc.close().syncUninterruptibly();
}
group.shutdownGracefully();
ReferenceCountUtil.release(sslServerCtx);
ReferenceCountUtil.release(sslClientCtx);
}
}
@Test(timeout = 300000)
public void testCompositeBufSizeEstimationGuaranteesSynchronousWrite()
throws CertificateException, SSLException, ExecutionException, InterruptedException {
SslProvider[] providers = SslProvider.values();
for (int i = 0; i < providers.length; ++i) {
SslProvider serverProvider = providers[i];
if (isSupported(serverProvider)) {
for (int j = 0; j < providers.length; ++j) {
SslProvider clientProvider = providers[j];
if (isSupported(clientProvider)) {
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
true, true);
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
true, false);
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
false, true);
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
false, false);
}
}
}
}
}
private static void compositeBufSizeEstimationGuaranteesSynchronousWrite(
SslProvider serverProvider, SslProvider clientProvider,
final boolean letHandlerCreateServerEngine, final boolean letHandlerCreateClientEngine)
throws CertificateException, SSLException, ExecutionException, InterruptedException {
SelfSignedCertificate ssc = new SelfSignedCertificate();
final SslContext sslServerCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(serverProvider)
.build();
final SslContext sslClientCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(clientProvider).build();
EventLoopGroup group = new NioEventLoopGroup();
Channel sc = null;
Channel cc = null;
try {
final Promise<Void> donePromise = group.next().newPromise();
// The goal is to provide the SSLEngine with many ByteBuf components to ensure that the overhead for wrap
// is correctly accounted for on each component.
final int numComponents = 150;
// This is the TLS packet size. The goal is to divide the maximum amount of application data that can fit
// into a single TLS packet into many components to ensure the overhead is correctly taken into account.
final int desiredBytes = 16384;
final int singleComponentSize = desiredBytes / numComponents;
final int expectedBytes = numComponents * singleComponentSize;
sc = new ServerBootstrap()
.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
if (letHandlerCreateServerEngine) {
ch.pipeline().addLast(sslServerCtx.newHandler(ch.alloc()));
} else {
ch.pipeline().addLast(new SslHandler(sslServerCtx.newEngine(ch.alloc())));
}
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof SslHandshakeCompletionEvent) {
SslHandshakeCompletionEvent sslEvt = (SslHandshakeCompletionEvent) evt;
if (sslEvt.isSuccess()) {
CompositeByteBuf content = ctx.alloc().compositeDirectBuffer(numComponents);
for (int i = 0; i < numComponents; ++i) {
ByteBuf buf = ctx.alloc().directBuffer(singleComponentSize);
buf.writerIndex(buf.writerIndex() + singleComponentSize);
content.addComponent(true, buf);
}
ctx.writeAndFlush(content);
} else {
donePromise.tryFailure(sslEvt.cause());
}
}
ctx.fireUserEventTriggered(evt);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
donePromise.tryFailure(cause);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
donePromise.tryFailure(new IllegalStateException("server closed"));
}
});
}
}).bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
cc = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
if (letHandlerCreateClientEngine) {
ch.pipeline().addLast(sslClientCtx.newHandler(ch.alloc()));
} else {
ch.pipeline().addLast(new SslHandler(sslClientCtx.newEngine(ch.alloc())));
}
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
private int bytesSeen;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof ByteBuf) {
bytesSeen += ((ByteBuf) msg).readableBytes();
if (bytesSeen == expectedBytes) {
donePromise.trySuccess(null);
}
}
ReferenceCountUtil.release(msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
donePromise.tryFailure(cause);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
donePromise.tryFailure(new IllegalStateException("client closed"));
}
});
}
}).connect(sc.localAddress()).syncUninterruptibly().channel();
donePromise.get();
} finally {
if (cc != null) {
cc.close().syncUninterruptibly();
}
if (sc != null) {
sc.close().syncUninterruptibly();
}
group.shutdownGracefully();
ReferenceCountUtil.release(sslServerCtx);
ReferenceCountUtil.release(sslClientCtx);
ssc.delete();
}
}
private static boolean isSupported(SslProvider provider) {
switch (provider) {
case OPENSSL:
case OPENSSL_REFCNT:
return OpenSsl.isAvailable();
default:
return true;
}
}
}