add TLS protocol selection to server

This commit is contained in:
Jörg Prante 2019-08-26 10:31:16 +02:00
parent 47a1176048
commit 1c1260bba6
8 changed files with 138 additions and 31 deletions

View file

@ -1,6 +1,6 @@
group = org.xbib group = org.xbib
name = netty-http name = netty-http
version = 4.1.39.0 version = 4.1.39.1
# netty # netty
netty.version = 4.1.39.Final netty.version = 4.1.39.Final

View file

@ -111,7 +111,21 @@ public final class Server implements AutoCloseable {
if (serverConfig.isDebug()) { if (serverConfig.isDebug()) {
bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getDebugLogLevel())); bootstrap.handler(new LoggingHandler("bootstrap-server", serverConfig.getDebugLogLevel()));
} }
DomainNameMapping<SslContext> domainNameMapping = createDomainNameMapping(); if (serverConfig.getDefaultDomain() == null) {
throw new IllegalStateException("no default named server (with name '*') configured, unable to continue");
}
DomainNameMapping<SslContext> domainNameMapping = null;
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) {
DomainNameMappingBuilder<SslContext> 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) { if (serverConfig.getAddress().getVersion().majorVersion() == 1) {
HttpChannelInitializer httpChannelInitializer = new HttpChannelInitializer(this, HttpChannelInitializer httpChannelInitializer = new HttpChannelInitializer(this,
serverConfig.getAddress(), domainNameMapping); serverConfig.getAddress(), domainNameMapping);
@ -160,13 +174,14 @@ public final class Server implements AutoCloseable {
* @throws IOException if channel future sync is interrupted * @throws IOException if channel future sync is interrupted
*/ */
public ChannelFuture accept() throws IOException { 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 { try {
this.channelFuture = bootstrap.bind(serverConfig.getAddress().getInetSocketAddress()).await().sync(); this.channelFuture = bootstrap.bind(httpAddress.getInetSocketAddress()).await().sync();
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new IOException(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; return channelFuture;
} }
@ -270,25 +285,6 @@ public final class Server implements AutoCloseable {
return channelClass; return channelClass;
} }
private DomainNameMapping<SslContext> createDomainNameMapping() {
if (serverConfig.getDefaultDomain() == null) {
throw new IllegalStateException("no default named server (with name '*') configured, unable to continue");
}
DomainNameMapping<SslContext> domainNameMapping = null;
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) {
DomainNameMappingBuilder<SslContext> 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 { static class HttpServerParentThreadFactory implements ThreadFactory {
private int number = 0; private int number = 0;
@ -453,12 +449,12 @@ public final class Server implements AutoCloseable {
return this; return this;
} }
public Builder setEnablCcompression(boolean enablCcompression) { public Builder enableCompression(boolean enableCompression) {
this.serverConfig.setCompression(enablCcompression); this.serverConfig.setCompression(enableCompression);
return this; return this;
} }
public Builder setEnableDecompression(boolean enableDecompression) { public Builder enableDeompression(boolean enableDecompression) {
this.serverConfig.setDecompression(enableDecompression); this.serverConfig.setDecompression(enableDecompression);
return this; return this;
} }
@ -468,6 +464,11 @@ public final class Server implements AutoCloseable {
return this; return this;
} }
public Builder setTransportLayerSecurityProtocols(String[] protocols) {
this.serverConfig.setProtocols(protocols);
return this;
}
public Builder addServer(Domain domain) { public Builder addServer(Domain domain) {
this.serverConfig.putDomain(domain); this.serverConfig.putDomain(domain);
logger.log(Level.FINE, "adding named server: " + domain); logger.log(Level.FINE, "adding named server: " + domain);

View file

@ -8,11 +8,16 @@ import io.netty.util.Mapping;
import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.ServerConfig; import org.xbib.netty.http.server.ServerConfig;
import java.net.InetSocketAddress; 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.SSLEngine;
import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLParameters;
public class ExtendedSNIHandler extends SniHandler { public class ExtendedSNIHandler extends SniHandler {
private static final Logger logger = Logger.getLogger(ExtendedSNIHandler.class.getName());
private final ServerConfig serverConfig; private final ServerConfig serverConfig;
private final HttpAddress httpAddress; private final HttpAddress httpAddress;
@ -39,6 +44,7 @@ public class ExtendedSNIHandler extends SniHandler {
SSLParameters params = engine.getSSLParameters(); SSLParameters params = engine.getSSLParameters();
params.setEndpointIdentificationAlgorithm("HTTPS"); params.setEndpointIdentificationAlgorithm("HTTPS");
engine.setSSLParameters(params); engine.setSSLParameters(params);
logger.log(Level.FINE, () -> "set enabled TLS protocols in SSL engine: " + Arrays.asList(serverConfig.getProtocols()));
engine.setEnabledProtocols(serverConfig.getProtocols()); engine.setEnabledProtocols(serverConfig.getProtocols());
return sslHandler; return sslHandler;
} }

View file

@ -107,7 +107,6 @@ public class HttpChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.log(Level.FINE, "channelRead: " + msg.getClass().getName());
if (msg instanceof HttpPipelinedRequest) { if (msg instanceof HttpPipelinedRequest) {
HttpPipelinedRequest httpPipelinedRequest = (HttpPipelinedRequest) msg; HttpPipelinedRequest httpPipelinedRequest = (HttpPipelinedRequest) msg;
if (httpPipelinedRequest.getRequest() instanceof FullHttpRequest) { if (httpPipelinedRequest.getRequest() instanceof FullHttpRequest) {

View file

@ -17,6 +17,7 @@ public class NettyHttpTestExtension implements BeforeAllCallback {
@Override @Override
public void beforeAll(ExtensionContext context) { public void beforeAll(ExtensionContext context) {
if (Security.getProvider("BC") == null) { if (Security.getProvider("BC") == null) {
// for insecure trust manager
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
System.setProperty("io.netty.noUnsafe", Boolean.toString(true)); System.setProperty("io.netty.noUnsafe", Boolean.toString(true));

View file

@ -38,7 +38,6 @@ class SecureHttp1Test {
.withContentType("text/plain") .withContentType("text/plain")
.write(request.getContent().retain())) .write(request.getContent().retain()))
.build()) .build())
.enableDebug()
.build(); .build();
Client client = Client.builder() Client client = Client.builder()
.trustInsecure() .trustInsecure()

View file

@ -55,8 +55,7 @@ class SecureHttp2Test {
String payload = 0 + "/" + 0; String payload = 0 + "/" + 0;
Request request = Request.get() Request request = Request.get()
.setVersion("HTTP/2.0") .setVersion("HTTP/2.0")
.uri("/") .url(server.getServerConfig().getAddress().base())
//.url(server.getServerConfig().getAddress().base())
.content(payload, "text/plain") .content(payload, "text/plain")
.build() .build()
.setResponseListener(responseListener); .setResponseListener(responseListener);

View file

@ -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<HttpResponse> 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<HttpResponse> 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());
}
}