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
name = netty-http
version = 4.1.39.0
version = 4.1.39.1
# netty
netty.version = 4.1.39.Final

View file

@ -111,7 +111,21 @@ public final class Server implements AutoCloseable {
if (serverConfig.isDebug()) {
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) {
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<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 {
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);

View file

@ -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;
}

View file

@ -107,7 +107,6 @@ public class HttpChannelInitializer extends ChannelInitializer<SocketChannel> {
@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) {

View file

@ -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));

View file

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

View file

@ -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);

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