add TLS protocol selection to server
This commit is contained in:
parent
47a1176048
commit
1c1260bba6
8 changed files with 138 additions and 31 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue