Use Parameterized to run SslHandler tests with different SslProviders.
Motivation: At the moment use loops to run SslHandler tests with different SslProviders which is error-prone and also make it difficult to understand with which provider these failed. Modifications: - Move unit tests that should run with multiple SslProviders to extra class. - Use junit Parameterized to run with different SslProvider combinations Result: Easier to understand which SslProvider produced test failures
This commit is contained in:
parent
cc069722a2
commit
2adb8bd80f
@ -0,0 +1,489 @@
|
||||
/*
|
||||
* Copyright 2017 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.CompositeByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
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.ReferenceCountUtil;
|
||||
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 org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import javax.net.ssl.ManagerFactoryParameters;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class ParameterizedSslHandlerTest {
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: clientProvider={0}, {index}: serverProvider={1}")
|
||||
public static Collection<Object[]> data() {
|
||||
List<SslProvider> providers = new ArrayList<SslProvider>(3);
|
||||
if (OpenSsl.isAvailable()) {
|
||||
providers.add(SslProvider.OPENSSL);
|
||||
providers.add(SslProvider.OPENSSL_REFCNT);
|
||||
}
|
||||
providers.add(SslProvider.JDK);
|
||||
|
||||
List<Object[]> params = new ArrayList<Object[]>();
|
||||
|
||||
for (SslProvider cp: providers) {
|
||||
for (SslProvider sp: providers) {
|
||||
params.add(new Object[] { cp, sp });
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private final SslProvider clientProvider;
|
||||
private final SslProvider serverProvider;
|
||||
|
||||
public ParameterizedSslHandlerTest(SslProvider clientProvider, SslProvider serverProvider) {
|
||||
this.clientProvider = clientProvider;
|
||||
this.serverProvider = serverProvider;
|
||||
}
|
||||
|
||||
@Test(timeout = 480000)
|
||||
public void testCompositeBufSizeEstimationGuaranteesSynchronousWrite()
|
||||
throws CertificateException, SSLException, ExecutionException, InterruptedException {
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
true, true, true);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
true, true, false);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
true, false, true);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
true, false, false);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
false, true, true);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
false, true, false);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
false, false, true);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
false, false, false);
|
||||
}
|
||||
|
||||
private static void compositeBufSizeEstimationGuaranteesSynchronousWrite(
|
||||
SslProvider serverProvider, SslProvider clientProvider,
|
||||
final boolean serverDisableWrapSize,
|
||||
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 {
|
||||
final SslHandler handler = letHandlerCreateServerEngine
|
||||
? sslServerCtx.newHandler(ch.alloc())
|
||||
: new SslHandler(sslServerCtx.newEngine(ch.alloc()));
|
||||
if (serverDisableWrapSize) {
|
||||
handler.setWrapDataSize(-1);
|
||||
}
|
||||
ch.pipeline().addLast(handler);
|
||||
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
|
||||
private boolean sentData;
|
||||
private Throwable writeCause;
|
||||
|
||||
@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).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
writeCause = future.cause();
|
||||
if (writeCause == null) {
|
||||
sentData = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
donePromise.tryFailure(sslEvt.cause());
|
||||
}
|
||||
}
|
||||
ctx.fireUserEventTriggered(evt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
donePromise.tryFailure(new IllegalStateException("server exception sentData: " +
|
||||
sentData + " writeCause: " + writeCause, cause));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
donePromise.tryFailure(new IllegalStateException("server closed sentData: " +
|
||||
sentData + " writeCause: " + writeCause));
|
||||
}
|
||||
});
|
||||
}
|
||||
}).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 userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
if (evt instanceof SslHandshakeCompletionEvent) {
|
||||
SslHandshakeCompletionEvent sslEvt = (SslHandshakeCompletionEvent) evt;
|
||||
if (!sslEvt.isSuccess()) {
|
||||
donePromise.tryFailure(sslEvt.cause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
donePromise.tryFailure(new IllegalStateException("client exception. bytesSeen: " +
|
||||
bytesSeen, cause));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
donePromise.tryFailure(new IllegalStateException("client closed. bytesSeen: " +
|
||||
bytesSeen));
|
||||
}
|
||||
});
|
||||
}
|
||||
}).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();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 30000)
|
||||
public void testAlertProducedAndSend() throws Exception {
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
|
||||
final SslContext sslServerCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.sslProvider(serverProvider)
|
||||
.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(clientProvider).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(timeout = 30000)
|
||||
public void testCloseNotify() throws Exception {
|
||||
testCloseNotify(5000, false);
|
||||
}
|
||||
|
||||
@Test(timeout = 30000)
|
||||
public void testCloseNotifyReceivedTimeout() throws Exception {
|
||||
testCloseNotify(100, true);
|
||||
}
|
||||
|
||||
@Test(timeout = 30000)
|
||||
public void testCloseNotifyNotWaitForResponse() throws Exception {
|
||||
testCloseNotify(0, false);
|
||||
}
|
||||
|
||||
private void testCloseNotify(final long closeNotifyReadTimeout, final boolean timeout) throws Exception {
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,11 +20,8 @@ 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.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
@ -42,7 +39,6 @@ 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.AbstractReferenceCounted;
|
||||
import io.netty.util.IllegalReferenceCountException;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
@ -50,30 +46,20 @@ 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.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
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 io.netty.buffer.Unpooled.wrappedBuffer;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
@ -361,115 +347,6 @@ public class SslHandlerTest {
|
||||
};
|
||||
}
|
||||
|
||||
@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());
|
||||
@ -513,42 +390,6 @@ public class SslHandlerTest {
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writingReadOnlyBufferDoesNotBreakAggregation() throws Exception {
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
@ -618,314 +459,4 @@ public class SslHandlerTest {
|
||||
ReferenceCountUtil.release(sslClientCtx);
|
||||
}
|
||||
}
|
||||
|
||||
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 = 480000)
|
||||
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)) {
|
||||
try {
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
true, true, true);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
true, true, false);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
true, false, true);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
true, false, false);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
false, true, true);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
false, true, false);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
false, false, true);
|
||||
compositeBufSizeEstimationGuaranteesSynchronousWrite(serverProvider, clientProvider,
|
||||
false, false, false);
|
||||
} catch (Throwable cause) {
|
||||
throw new RuntimeException("serverProvider: " + serverProvider + " clientProvider: " +
|
||||
clientProvider, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void compositeBufSizeEstimationGuaranteesSynchronousWrite(
|
||||
SslProvider serverProvider, SslProvider clientProvider,
|
||||
final boolean serverDisableWrapSize,
|
||||
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 {
|
||||
final SslHandler handler = letHandlerCreateServerEngine
|
||||
? sslServerCtx.newHandler(ch.alloc())
|
||||
: new SslHandler(sslServerCtx.newEngine(ch.alloc()));
|
||||
if (serverDisableWrapSize) {
|
||||
handler.setWrapDataSize(-1);
|
||||
}
|
||||
ch.pipeline().addLast(handler);
|
||||
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
|
||||
private boolean sentData;
|
||||
private Throwable writeCause;
|
||||
|
||||
@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).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
writeCause = future.cause();
|
||||
if (writeCause == null) {
|
||||
sentData = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
donePromise.tryFailure(sslEvt.cause());
|
||||
}
|
||||
}
|
||||
ctx.fireUserEventTriggered(evt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
donePromise.tryFailure(new IllegalStateException("server exception sentData: " +
|
||||
sentData + " writeCause: " + writeCause, cause));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
donePromise.tryFailure(new IllegalStateException("server closed sentData: " +
|
||||
sentData + " writeCause: " + writeCause));
|
||||
}
|
||||
});
|
||||
}
|
||||
}).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 userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
if (evt instanceof SslHandshakeCompletionEvent) {
|
||||
SslHandshakeCompletionEvent sslEvt = (SslHandshakeCompletionEvent) evt;
|
||||
if (!sslEvt.isSuccess()) {
|
||||
donePromise.tryFailure(sslEvt.cause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
donePromise.tryFailure(new IllegalStateException("client exception. bytesSeen: " +
|
||||
bytesSeen, cause));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
donePromise.tryFailure(new IllegalStateException("client closed. bytesSeen: " +
|
||||
bytesSeen));
|
||||
}
|
||||
});
|
||||
}
|
||||
}).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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user