From 1c1260bba682a7047fe6705a6ace16e8faa73acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Mon, 26 Aug 2019 10:31:16 +0200 Subject: [PATCH] add TLS protocol selection to server --- gradle.properties | 2 +- .../org/xbib/netty/http/server/Server.java | 53 ++++----- .../server/handler/ExtendedSNIHandler.java | 6 ++ .../handler/http/HttpChannelInitializer.java | 1 - .../server/test/NettyHttpTestExtension.java | 1 + .../http/server/test/SecureHttp1Test.java | 1 - .../http/server/test/SecureHttp2Test.java | 3 +- .../TransportLayerSecurityServerTest.java | 102 ++++++++++++++++++ 8 files changed, 138 insertions(+), 31 deletions(-) create mode 100644 netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java diff --git a/gradle.properties b/gradle.properties index 255704b..b070a93 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ group = org.xbib name = netty-http -version = 4.1.39.0 +version = 4.1.39.1 # netty netty.version = 4.1.39.Final diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java index 39a98f2..f0f7ba9 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java @@ -111,7 +111,21 @@ public final class Server implements AutoCloseable { if (serverConfig.isDebug()) { bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getDebugLogLevel())); } - DomainNameMapping domainNameMapping = createDomainNameMapping(); + if (serverConfig.getDefaultDomain() == null) { + throw new IllegalStateException("no default named server (with name '*') configured, unable to continue"); + } + DomainNameMapping domainNameMapping = null; + if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) { + DomainNameMappingBuilder mappingBuilder = + new DomainNameMappingBuilder<>(serverConfig.getDefaultDomain().getSslContext()); + for (Domain domain : serverConfig.getDomains()) { + String name = domain.getName(); + if (!"*".equals(name)) { + mappingBuilder.add(name, domain.getSslContext()); + } + } + domainNameMapping = mappingBuilder.build(); + } if (serverConfig.getAddress().getVersion().majorVersion() == 1) { HttpChannelInitializer httpChannelInitializer = new HttpChannelInitializer(this, serverConfig.getAddress(), domainNameMapping); @@ -160,13 +174,14 @@ public final class Server implements AutoCloseable { * @throws IOException if channel future sync is interrupted */ public ChannelFuture accept() throws IOException { - logger.log(Level.INFO, () -> "trying to bind to " + serverConfig.getAddress()); + HttpAddress httpAddress = serverConfig.getAddress(); + logger.log(Level.INFO, () -> "trying to bind to " + httpAddress); try { - this.channelFuture = bootstrap.bind(serverConfig.getAddress().getInetSocketAddress()).await().sync(); + this.channelFuture = bootstrap.bind(httpAddress.getInetSocketAddress()).await().sync(); } catch (InterruptedException e) { throw new IOException(e); } - logger.log(Level.INFO, () -> ServerName.getServerName() + " ready, listening on " + serverConfig.getAddress()); + logger.log(Level.INFO, () -> ServerName.getServerName() + " ready, listening on " + httpAddress); return channelFuture; } @@ -270,25 +285,6 @@ public final class Server implements AutoCloseable { return channelClass; } - private DomainNameMapping createDomainNameMapping() { - if (serverConfig.getDefaultDomain() == null) { - throw new IllegalStateException("no default named server (with name '*') configured, unable to continue"); - } - DomainNameMapping domainNameMapping = null; - if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) { - DomainNameMappingBuilder mappingBuilder = - new DomainNameMappingBuilder<>(serverConfig.getDefaultDomain().getSslContext()); - for (Domain domain : serverConfig.getDomains()) { - String name = domain.getName(); - if (!"*".equals(name)) { - mappingBuilder.add(name, domain.getSslContext()); - } - } - domainNameMapping = mappingBuilder.build(); - } - return domainNameMapping; - } - static class HttpServerParentThreadFactory implements ThreadFactory { private int number = 0; @@ -453,12 +449,12 @@ public final class Server implements AutoCloseable { return this; } - public Builder setEnablCcompression(boolean enablCcompression) { - this.serverConfig.setCompression(enablCcompression); + public Builder enableCompression(boolean enableCompression) { + this.serverConfig.setCompression(enableCompression); return this; } - public Builder setEnableDecompression(boolean enableDecompression) { + public Builder enableDeompression(boolean enableDecompression) { this.serverConfig.setDecompression(enableDecompression); return this; } @@ -468,6 +464,11 @@ public final class Server implements AutoCloseable { return this; } + public Builder setTransportLayerSecurityProtocols(String[] protocols) { + this.serverConfig.setProtocols(protocols); + return this; + } + public Builder addServer(Domain domain) { this.serverConfig.putDomain(domain); logger.log(Level.FINE, "adding named server: " + domain); diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/ExtendedSNIHandler.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/ExtendedSNIHandler.java index 986c05a..96972d8 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/ExtendedSNIHandler.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/ExtendedSNIHandler.java @@ -8,11 +8,16 @@ import io.netty.util.Mapping; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.server.ServerConfig; import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; public class ExtendedSNIHandler extends SniHandler { + private static final Logger logger = Logger.getLogger(ExtendedSNIHandler.class.getName()); + private final ServerConfig serverConfig; private final HttpAddress httpAddress; @@ -39,6 +44,7 @@ public class ExtendedSNIHandler extends SniHandler { SSLParameters params = engine.getSSLParameters(); params.setEndpointIdentificationAlgorithm("HTTPS"); engine.setSSLParameters(params); + logger.log(Level.FINE, () -> "set enabled TLS protocols in SSL engine: " + Arrays.asList(serverConfig.getProtocols())); engine.setEnabledProtocols(serverConfig.getProtocols()); return sslHandler; } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpChannelInitializer.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpChannelInitializer.java index 9fa4a77..b9de484 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpChannelInitializer.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/handler/http/HttpChannelInitializer.java @@ -107,7 +107,6 @@ public class HttpChannelInitializer extends ChannelInitializer { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - logger.log(Level.FINE, "channelRead: " + msg.getClass().getName()); if (msg instanceof HttpPipelinedRequest) { HttpPipelinedRequest httpPipelinedRequest = (HttpPipelinedRequest) msg; if (httpPipelinedRequest.getRequest() instanceof FullHttpRequest) { diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/NettyHttpTestExtension.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/NettyHttpTestExtension.java index 9162c50..a403d99 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/NettyHttpTestExtension.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/NettyHttpTestExtension.java @@ -17,6 +17,7 @@ public class NettyHttpTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { if (Security.getProvider("BC") == null) { + // for insecure trust manager Security.addProvider(new BouncyCastleProvider()); } System.setProperty("io.netty.noUnsafe", Boolean.toString(true)); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp1Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp1Test.java index 26e3b46..2b73ce7 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp1Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp1Test.java @@ -38,7 +38,6 @@ class SecureHttp1Test { .withContentType("text/plain") .write(request.getContent().retain())) .build()) - .enableDebug() .build(); Client client = Client.builder() .trustInsecure() diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp2Test.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp2Test.java index 37a035b..6c2e2d6 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp2Test.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureHttp2Test.java @@ -55,8 +55,7 @@ class SecureHttp2Test { String payload = 0 + "/" + 0; Request request = Request.get() .setVersion("HTTP/2.0") - .uri("/") - //.url(server.getServerConfig().getAddress().base()) + .url(server.getServerConfig().getAddress().base()) .content(payload, "text/plain") .build() .setResponseListener(responseListener); diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java new file mode 100644 index 0000000..24f55ff --- /dev/null +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/TransportLayerSecurityServerTest.java @@ -0,0 +1,102 @@ +package org.xbib.netty.http.server.test; + +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.xbib.netty.http.client.Client; +import org.xbib.netty.http.client.Request; +import org.xbib.netty.http.client.listener.ResponseListener; +import org.xbib.netty.http.client.transport.Transport; +import org.xbib.netty.http.common.HttpAddress; +import org.xbib.netty.http.common.HttpResponse; +import org.xbib.netty.http.server.Domain; +import org.xbib.netty.http.server.Server; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +@ExtendWith(NettyHttpTestExtension.class) +class TransportLayerSecurityServerTest { + + private static final Logger logger = Logger.getLogger(TransportLayerSecurityServerTest.class.getName()); + + @Test + void testTLS12() throws Exception { + HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143); + Server server = Server.builder(Domain.builder(httpAddress) + .setSelfCert() + .singleEndpoint("/", (request, response) -> + response.withStatus(HttpResponseStatus.OK) + .withContentType("text/plain") + .write(request.getContent().retain())) + .build()) + .setTransportLayerSecurityProtocols(new String[]{ "TLSv1.2"}) + .build(); + Client client = Client.builder() + .trustInsecure() + .build(); + AtomicInteger counter = new AtomicInteger(); + final ResponseListener responseListener = resp -> { + logger.log(Level.INFO, "response listener: headers = " + resp.getHeaders() + + " response body = " + resp.getBodyAsString(StandardCharsets.UTF_8)); + counter.incrementAndGet(); + }; + try { + server.accept(); + Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base()) + .content("Hello Jörg", "text/plain") + .build() + .setResponseListener(responseListener); + Transport transport = client.execute(request).get(); + logger.log(Level.INFO, "HTTP 1.1 TLS protocol = " + transport.getSession().getProtocol()); + assertEquals("TLSv1.2", transport.getSession().getProtocol()); + } finally { + client.shutdownGracefully(); + server.shutdownGracefully(); + } + assertEquals(1, counter.get()); + } + + @Test + void testTLS13() throws Exception { + HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143); + Server server = Server.builder(Domain.builder(httpAddress) + .setSelfCert() + .singleEndpoint("/", (request, response) -> + response.withStatus(HttpResponseStatus.OK) + .withContentType("text/plain") + .write(request.getContent().retain())) + .build()) + .setTransportLayerSecurityProtocols(new String[]{ "TLSv1.3"}) + .build(); + Client client = Client.builder() + .trustInsecure() + .build(); + AtomicInteger counter = new AtomicInteger(); + final ResponseListener responseListener = resp -> { + logger.log(Level.INFO, "response listener: headers = " + resp.getHeaders() + + " response body = " + resp.getBodyAsString(StandardCharsets.UTF_8)); + counter.incrementAndGet(); + }; + try { + server.accept(); + Request request = Request.get() + .setVersion("HTTP/2.0") + .url(server.getServerConfig().getAddress().base()) + .content("Hello Jörg", "text/plain") + .build() + .setResponseListener(responseListener); + Transport transport = client.execute(request).get(); + logger.log(Level.INFO, "HTTP/2 TLS protocol = " + transport.getSession().getProtocol()); + assertEquals("TLSv1.3", transport.getSession().getProtocol()); + } finally { + client.shutdownGracefully(); + server.shutdownGracefully(); + } + assertEquals(1, counter.get()); + } +}