[#5874] [#5971] Ensure SniHandlerTest.testServerNameParsing not fails with SslProvider.JDK

Motivation:

The SniHandlerTest.testServerNameParsing did fail when SslProvider.JDK was used as it the JDK SSLEngineImpl does not send an alert.

Modifications:

Ensure tests pass with JDK and OPENSSL ssl implementations.

Result:

SniHandlerTest will run with all SslProvider and not fail when SslProvider.JDK is used.
This commit is contained in:
Norman Maurer 2016-11-16 07:59:21 +01:00
parent 595b2676be
commit d80bd8fb4a

View File

@ -16,6 +16,24 @@
package io.netty.handler.ssl;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.xml.bind.DatatypeConverter;
import org.junit.Test;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
@ -33,20 +51,11 @@ import io.netty.handler.codec.DecoderException;
import io.netty.util.DomainNameMapping;
import io.netty.util.DomainNameMappingBuilder;
import io.netty.util.ReferenceCountUtil;
import org.junit.Test;
import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class SniHandlerTest {
private static ApplicationProtocolConfig newApnConfig() {
@ -59,174 +68,244 @@ public class SniHandlerTest {
"myprotocol");
}
private static SslContext makeSslContext() throws Exception {
private static void assumeApnSupported(SslProvider provider) {
switch (provider) {
case OPENSSL:
case OPENSSL_REFCNT:
assumeTrue(OpenSsl.isAlpnSupported());
break;
case JDK:
assumeTrue(JdkAlpnSslEngine.isAvailable());
break;
default:
throw new Error();
}
}
private static SslContext makeSslContext(SslProvider provider, boolean apn) throws Exception {
if (apn) {
assumeApnSupported(provider);
}
File keyFile = new File(SniHandlerTest.class.getResource("test_encrypted.pem").getFile());
File crtFile = new File(SniHandlerTest.class.getResource("test.crt").getFile());
return SslContextBuilder.forServer(crtFile, keyFile, "12345").applicationProtocolConfig(newApnConfig()).build();
SslContextBuilder sslCtxBuilder = SslContextBuilder.forServer(crtFile, keyFile, "12345")
.sslProvider(provider);
if (apn) {
sslCtxBuilder.applicationProtocolConfig(newApnConfig());
}
return sslCtxBuilder.build();
}
private static SslContext makeSslClientContext() throws Exception {
private static SslContext makeSslClientContext(SslProvider provider, boolean apn) throws Exception {
if (apn) {
assumeApnSupported(provider);
}
File crtFile = new File(SniHandlerTest.class.getResource("test.crt").getFile());
return SslContextBuilder.forClient().trustManager(crtFile).applicationProtocolConfig(newApnConfig()).build();
SslContextBuilder sslCtxBuilder = SslContextBuilder.forClient().trustManager(crtFile).sslProvider(provider);
if (apn) {
sslCtxBuilder.applicationProtocolConfig(newApnConfig());
}
return sslCtxBuilder.build();
}
@Parameterized.Parameters(name = "{index}: sslProvider={0}")
public static Iterable<? extends Object> data() {
List<SslProvider> params = new ArrayList<SslProvider>(3);
if (OpenSsl.isAvailable()) {
params.add(SslProvider.OPENSSL);
params.add(SslProvider.OPENSSL_REFCNT);
}
params.add(SslProvider.JDK);
return params;
}
private final SslProvider provider;
public SniHandlerTest(SslProvider provider) {
this.provider = provider;
}
@Test
public void testServerNameParsing() throws Exception {
SslContext nettyContext = makeSslContext();
SslContext leanContext = makeSslContext();
SslContext leanContext2 = makeSslContext();
DomainNameMapping<SslContext> mapping = new DomainNameMappingBuilder<SslContext>(nettyContext)
.add("*.netty.io", nettyContext)
// input with custom cases
.add("*.LEANCLOUD.CN", leanContext)
// a hostname conflict with previous one, since we are using order-sensitive config, the engine won't
// be used with the handler.
.add("chat4.leancloud.cn", leanContext2)
.build();
SniHandler handler = new SniHandler(mapping);
EmbeddedChannel ch = new EmbeddedChannel(handler);
// hex dump of a client hello packet, which contains hostname "CHAT4。LEANCLOUD。CN"
String tlsHandshakeMessageHex1 = "16030100";
// part 2
String tlsHandshakeMessageHex = "bd010000b90303a74225676d1814ba57faff3b366" +
"3656ed05ee9dbb2a4dbb1bb1c32d2ea5fc39e0000000100008c0000001700150000164348" +
"415434E380824C45414E434C4F5544E38082434E000b000403000102000a00340032000e0" +
"00d0019000b000c00180009000a0016001700080006000700140015000400050012001300" +
"0100020003000f0010001100230000000d0020001e0601060206030501050205030401040" +
"20403030103020303020102020203000f00010133740000";
SslContext nettyContext = makeSslContext(provider, false);
SslContext leanContext = makeSslContext(provider, false);
SslContext leanContext2 = makeSslContext(provider, false);
try {
// Push the handshake message.
// Decode should fail because SNI error
ch.writeInbound(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex1)));
ch.writeInbound(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex)));
fail();
} catch (DecoderException e) {
// expected
}
DomainNameMapping<SslContext> mapping = new DomainNameMappingBuilder<SslContext>(nettyContext)
.add("*.netty.io", nettyContext)
// input with custom cases
.add("*.LEANCLOUD.CN", leanContext)
// a hostname conflict with previous one, since we are using order-sensitive config,
// the engine won't be used with the handler.
.add("chat4.leancloud.cn", leanContext2)
.build();
assertThat(ch.finish(), is(true));
assertThat(handler.hostname(), is("chat4.leancloud.cn"));
assertThat(handler.sslContext(), is(leanContext));
SniHandler handler = new SniHandler(mapping);
EmbeddedChannel ch = new EmbeddedChannel(handler);
for (;;) {
Object msg = ch.readOutbound();
if (msg == null) {
break;
// hex dump of a client hello packet, which contains hostname "CHAT4。LEANCLOUD。CN"
String tlsHandshakeMessageHex1 = "16030100";
// part 2
String tlsHandshakeMessageHex = "bd010000b90303a74225676d1814ba57faff3b366" +
"3656ed05ee9dbb2a4dbb1bb1c32d2ea5fc39e0000000100008c0000001700150000164348" +
"415434E380824C45414E434C4F5544E38082434E000b000403000102000a00340032000e0" +
"00d0019000b000c00180009000a0016001700080006000700140015000400050012001300" +
"0100020003000f0010001100230000000d0020001e0601060206030501050205030401040" +
"20403030103020303020102020203000f00010133740000";
try {
// Push the handshake message.
// Decode should fail because SNI error
ch.writeInbound(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex1)));
ch.writeInbound(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex)));
fail();
} catch (DecoderException e) {
// expected
}
ReferenceCountUtil.release(msg);
// Just call finish and not assert the return value. This is because OpenSSL correct produces an alert
// while the JDK SSLEngineImpl does not atm.
// See https://github.com/netty/netty/issues/5874
ch.finish();
assertThat(handler.hostname(), is("chat4.leancloud.cn"));
assertThat(handler.sslContext(), is(leanContext));
for (;;) {
Object msg = ch.readOutbound();
if (msg == null) {
break;
}
ReferenceCountUtil.release(msg);
}
} finally {
releaseAll(leanContext, leanContext2, nettyContext);
}
}
@Test
public void testFallbackToDefaultContext() throws Exception {
SslContext nettyContext = makeSslContext();
SslContext leanContext = makeSslContext();
SslContext leanContext2 = makeSslContext();
DomainNameMapping<SslContext> mapping = new DomainNameMappingBuilder<SslContext>(nettyContext)
.add("*.netty.io", nettyContext)
// input with custom cases
.add("*.LEANCLOUD.CN", leanContext)
// a hostname conflict with previous one, since we are using order-sensitive config, the engine won't
// be used with the handler.
.add("chat4.leancloud.cn", leanContext2)
.build();
SniHandler handler = new SniHandler(mapping);
EmbeddedChannel ch = new EmbeddedChannel(handler);
// invalid
byte[] message = { 22, 3, 1, 0, 0 };
SslContext nettyContext = makeSslContext(provider, false);
SslContext leanContext = makeSslContext(provider, false);
SslContext leanContext2 = makeSslContext(provider, false);
try {
// Push the handshake message.
ch.writeInbound(Unpooled.wrappedBuffer(message));
} catch (Exception e) {
// expected
}
DomainNameMapping<SslContext> mapping = new DomainNameMappingBuilder<SslContext>(nettyContext)
.add("*.netty.io", nettyContext)
// input with custom cases
.add("*.LEANCLOUD.CN", leanContext)
// a hostname conflict with previous one, since we are using order-sensitive config,
// the engine won't be used with the handler.
.add("chat4.leancloud.cn", leanContext2)
.build();
assertThat(ch.finish(), is(false));
assertThat(handler.hostname(), nullValue());
assertThat(handler.sslContext(), is(nettyContext));
SniHandler handler = new SniHandler(mapping);
EmbeddedChannel ch = new EmbeddedChannel(handler);
// invalid
byte[] message = {22, 3, 1, 0, 0};
try {
// Push the handshake message.
ch.writeInbound(Unpooled.wrappedBuffer(message));
} catch (Exception e) {
// expected
}
assertThat(ch.finish(), is(false));
assertThat(handler.hostname(), nullValue());
assertThat(handler.sslContext(), is(nettyContext));
} finally {
releaseAll(leanContext, leanContext2, nettyContext);
}
}
@Test
public void testSniWithApnHandler() throws Exception {
SslContext nettyContext = makeSslContext();
SslContext sniContext = makeSslContext();
final SslContext clientContext = makeSslClientContext();
final CountDownLatch serverApnDoneLatch = new CountDownLatch(1);
final CountDownLatch clientApnDoneLatch = new CountDownLatch(1);
final DomainNameMapping<SslContext> mapping = new DomainNameMappingBuilder<SslContext>(nettyContext)
.add("*.netty.io", nettyContext)
.add("sni.fake.site", sniContext).build();
final SniHandler handler = new SniHandler(mapping);
EventLoopGroup group = new NioEventLoopGroup(2);
Channel serverChannel = null;
Channel clientChannel = null;
SslContext nettyContext = makeSslContext(provider, true);
SslContext sniContext = makeSslContext(provider, true);
final SslContext clientContext = makeSslClientContext(provider, true);
try {
ServerBootstrap sb = new ServerBootstrap();
sb.group(group);
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
// Server side SNI.
p.addLast(handler);
// Catch the notification event that APN has completed successfully.
p.addLast(new ApplicationProtocolNegotiationHandler("foo") {
@Override
protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception {
serverApnDoneLatch.countDown();
}
});
final CountDownLatch serverApnDoneLatch = new CountDownLatch(1);
final CountDownLatch clientApnDoneLatch = new CountDownLatch(1);
final DomainNameMapping<SslContext> mapping = new DomainNameMappingBuilder<SslContext>(nettyContext)
.add("*.netty.io", nettyContext)
.add("sni.fake.site", sniContext).build();
final SniHandler handler = new SniHandler(mapping);
EventLoopGroup group = new NioEventLoopGroup(2);
Channel serverChannel = null;
Channel clientChannel = null;
try {
ServerBootstrap sb = new ServerBootstrap();
sb.group(group);
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
// Server side SNI.
p.addLast(handler);
// Catch the notification event that APN has completed successfully.
p.addLast(new ApplicationProtocolNegotiationHandler("foo") {
@Override
protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
serverApnDoneLatch.countDown();
}
});
}
});
Bootstrap cb = new Bootstrap();
cb.group(group);
cb.channel(NioSocketChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new SslHandler(clientContext.newEngine(
ch.alloc(), "sni.fake.site", -1)));
// Catch the notification event that APN has completed successfully.
ch.pipeline().addLast(new ApplicationProtocolNegotiationHandler("foo") {
@Override
protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
clientApnDoneLatch.countDown();
}
});
}
});
serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel();
ChannelFuture ccf = cb.connect(serverChannel.localAddress());
assertTrue(ccf.awaitUninterruptibly().isSuccess());
clientChannel = ccf.channel();
assertTrue(serverApnDoneLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientApnDoneLatch.await(5, TimeUnit.SECONDS));
assertThat(handler.hostname(), is("sni.fake.site"));
assertThat(handler.sslContext(), is(sniContext));
} finally {
if (serverChannel != null) {
serverChannel.close().sync();
}
});
Bootstrap cb = new Bootstrap();
cb.group(group);
cb.channel(NioSocketChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new SslHandler(clientContext.newEngine(
ch.alloc(), "sni.fake.site", -1)));
// Catch the notification event that APN has completed successfully.
ch.pipeline().addLast(new ApplicationProtocolNegotiationHandler("foo") {
@Override
protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception {
clientApnDoneLatch.countDown();
}
});
if (clientChannel != null) {
clientChannel.close().sync();
}
});
serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel();
ChannelFuture ccf = cb.connect(serverChannel.localAddress());
assertTrue(ccf.awaitUninterruptibly().isSuccess());
clientChannel = ccf.channel();
assertTrue(serverApnDoneLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientApnDoneLatch.await(5, TimeUnit.SECONDS));
assertThat(handler.hostname(), is("sni.fake.site"));
assertThat(handler.sslContext(), is(sniContext));
group.shutdownGracefully(0, 0, TimeUnit.MICROSECONDS);
}
} finally {
if (serverChannel != null) {
serverChannel.close().sync();
}
if (clientChannel != null) {
clientChannel.close().sync();
}
group.shutdownGracefully(0, 0, TimeUnit.MICROSECONDS);
releaseAll(clientContext, nettyContext, sniContext);
}
}
private static void releaseAll(SslContext... contexts) {
for (SslContext ctx: contexts) {
ReferenceCountUtil.release(ctx);
}
}
}