diff --git a/src/main/asciidoc/dataobjects.adoc b/src/main/asciidoc/dataobjects.adoc index 173875e448e..2d557497b41 100644 --- a/src/main/asciidoc/dataobjects.adoc +++ b/src/main/asciidoc/dataobjects.adoc @@ -826,6 +826,10 @@ Set true when the client wants to skip frame masking. You may want to set it true on server by server websocket communication: In this case you are by passing RFC6455 protocol. It's false as default. +++ +|[[sni]]`sni`|`Boolean`| ++++ +Set wether the client should present the request host with the Server Name Indication when using TLS/SSL connections. ++++ |[[soLinger]]`soLinger`|`Number (int)`| +++ Set whether SO_linger keep alive is enabled @@ -1841,10 +1845,6 @@ Set the host name to be used by the client request. +++ Set the port to be used by the client request. +++ -|[[serverName]]`serverName`|`String`| -+++ -Set the SNI server name to be used by the client connection. -+++ |[[ssl]]`ssl`|`Boolean`| +++ Set whether SSL/TLS is enabled diff --git a/src/main/generated/io/vertx/core/http/HttpClientOptionsConverter.java b/src/main/generated/io/vertx/core/http/HttpClientOptionsConverter.java index 863085885a3..9c34cac9601 100644 --- a/src/main/generated/io/vertx/core/http/HttpClientOptionsConverter.java +++ b/src/main/generated/io/vertx/core/http/HttpClientOptionsConverter.java @@ -95,6 +95,9 @@ public static void fromJson(JsonObject json, HttpClientOptions obj) { if (json.getValue("sendUnmaskedFrames") instanceof Boolean) { obj.setSendUnmaskedFrames((Boolean)json.getValue("sendUnmaskedFrames")); } + if (json.getValue("sni") instanceof Boolean) { + obj.setSni((Boolean)json.getValue("sni")); + } if (json.getValue("tryUseCompression") instanceof Boolean) { obj.setTryUseCompression((Boolean)json.getValue("tryUseCompression")); } @@ -135,6 +138,7 @@ public static void toJson(HttpClientOptions obj, JsonObject json) { json.put("protocolVersion", obj.getProtocolVersion().name()); } json.put("sendUnmaskedFrames", obj.isSendUnmaskedFrames()); + json.put("sni", obj.isSni()); json.put("tryUseCompression", obj.isTryUseCompression()); json.put("verifyHost", obj.isVerifyHost()); } diff --git a/src/main/java/io/vertx/core/http/HttpClientOptions.java b/src/main/java/io/vertx/core/http/HttpClientOptions.java index 363939b92f5..d0dbc943594 100644 --- a/src/main/java/io/vertx/core/http/HttpClientOptions.java +++ b/src/main/java/io/vertx/core/http/HttpClientOptions.java @@ -144,6 +144,11 @@ public class HttpClientOptions extends ClientOptionsBase { */ public static final int DEFAULT_MAX_REDIRECTS = 16; + /* + * Default sni = false + */ + public static final boolean DEFAULT_SNI = false; + private boolean verifyHost = true; private int maxPoolSize; private boolean keepAlive; @@ -168,6 +173,7 @@ public class HttpClientOptions extends ClientOptionsBase { private boolean http2ClearTextUpgrade; private boolean sendUnmaskedFrames; private int maxRedirects; + private boolean sni; /** * Default constructor @@ -207,6 +213,7 @@ public HttpClientOptions(HttpClientOptions other) { this.http2ClearTextUpgrade = other.http2ClearTextUpgrade; this.sendUnmaskedFrames = other.isSendUnmaskedFrames(); this.maxRedirects = other.maxRedirects; + this.sni = other.sni; } /** @@ -255,6 +262,7 @@ private void init() { http2ClearTextUpgrade = DEFAULT_HTTP2_CLEAR_TEXT_UPGRADE; sendUnmaskedFrames = DEFAULT_SEND_UNMASKED_FRAMES; maxRedirects = DEFAULT_MAX_REDIRECTS; + sni = DEFAULT_SNI; } @Override @@ -888,6 +896,24 @@ public HttpClientOptions setMaxRedirects(int maxRedirects) { return this; } + /** + * @return wether the client should use SNI on TLS/SSL connections + */ + public boolean isSni() { + return sni; + } + + /** + * Set wether the client should present the request host with the Server Name Indication when using TLS/SSL connections. + * + * @param sni true when the client should use SNI on TLS/SSL connections + * @return a reference to this, so the API can be used fluently + */ + public HttpClientOptions setSni(boolean sni) { + this.sni = sni; + return this; + } + public HttpClientOptions setMetricsName(String metricsName) { return (HttpClientOptions) super.setMetricsName(metricsName); } diff --git a/src/main/java/io/vertx/core/http/RequestOptions.java b/src/main/java/io/vertx/core/http/RequestOptions.java index 2c76213d92c..8c8de4dfdab 100644 --- a/src/main/java/io/vertx/core/http/RequestOptions.java +++ b/src/main/java/io/vertx/core/http/RequestOptions.java @@ -46,12 +46,6 @@ public class RequestOptions { */ public static final String DEFAULT_URI = ""; - /** - * SNI default serve name = null - */ - public static final String DEFAULT_SERVER_NAME = null; - - private String serverName; private String host; private int port; private boolean ssl; @@ -61,7 +55,6 @@ public class RequestOptions { * Default constructor */ public RequestOptions() { - serverName = DEFAULT_SERVER_NAME; host = DEFAULT_HOST; port = DEFAULT_PORT; ssl = DEFAULT_SSL; @@ -93,25 +86,6 @@ public RequestOptions(JsonObject json) { setURI(json.getString("uri", DEFAULT_URI)); } - /** - * Get the SNI server name to be used by the client connection. - * - * @return the SNI server name - */ - public String getServerName() { - return serverName; - } - - /** - * Set the SNI server name to be used by the client connection. - * - * @return a reference to this, so the API can be used fluently - */ - public RequestOptions setServerName(String serverName) { - this.serverName = serverName; - return this; - } - /** * Get the host name to be used by the client request. * diff --git a/src/main/java/io/vertx/core/http/impl/ConnectionManager.java b/src/main/java/io/vertx/core/http/impl/ConnectionManager.java index e99244ee2de..5648e356a05 100644 --- a/src/main/java/io/vertx/core/http/impl/ConnectionManager.java +++ b/src/main/java/io/vertx/core/http/impl/ConnectionManager.java @@ -101,13 +101,11 @@ HttpClientMetrics metrics() { static final class ConnectionKey { - private final String serverName; private final boolean ssl; private final int port; private final String host; - public ConnectionKey(String serverName, boolean ssl, int port, String host) { - this.serverName = serverName; + public ConnectionKey(boolean ssl, int port, String host) { this.ssl = ssl; this.host = host; this.port = port; @@ -120,7 +118,6 @@ public boolean equals(Object o) { ConnectionKey that = (ConnectionKey) o; - if (!Objects.equals(serverName, that.serverName)) return false; if (ssl != that.ssl) return false; if (port != that.port) return false; if (!Objects.equals(host, that.host)) return false; @@ -131,7 +128,6 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = ssl ? 1 : 0; - result = 31 * result + (serverName != null ? serverName.hashCode() : 0); result = 31 * result + (host != null ? host.hashCode() : 0); result = 31 * result + port; return result; @@ -149,8 +145,9 @@ private class QueueManager { private final Map connectionMap = new ConcurrentHashMap<>(); private final Map queueMap = new ConcurrentHashMap<>(); - ConnQueue getConnQueue(ConnectionKey address, HttpVersion version) { - return queueMap.computeIfAbsent(address, targetAddress -> new ConnQueue(version, this, targetAddress)); + ConnQueue getConnQueue(String peerHost, boolean ssl, int port, String host, HttpVersion version) { + ConnectionKey key = new ConnectionKey(ssl, port, ssl && peerHost != null ? peerHost : host); + return queueMap.computeIfAbsent(key, targetAddress -> new ConnQueue(version, this, peerHost, host, port, ssl, key)); } public void close() { @@ -165,17 +162,15 @@ public void close() { } public void getConnectionForWebsocket(boolean ssl, int port, String host, Waiter waiter) { - ConnectionKey address = new ConnectionKey(null, ssl, port, host); - ConnQueue connQueue = wsQM.getConnQueue(address, HttpVersion.HTTP_1_1); + ConnQueue connQueue = wsQM.getConnQueue(host, ssl, port, host, HttpVersion.HTTP_1_1); connQueue.getConnection(waiter); } - public void getConnectionForRequest(String serverName, boolean ssl, HttpVersion version, int port, String host, Waiter waiter) { + public void getConnectionForRequest(HttpVersion version, String peerHost, boolean ssl, int port, String host, Waiter waiter) { if (!keepAlive && pipelining) { waiter.handleFailure(new IllegalStateException("Cannot have pipelining with no keep alive")); } else { - ConnectionKey address = new ConnectionKey(serverName, ssl, port, host); - ConnQueue connQueue = requestQM.getConnQueue(address, version); + ConnQueue connQueue = requestQM.getConnQueue(peerHost, ssl, port, host, version); connQueue.getConnection(waiter); } } @@ -199,15 +194,23 @@ public void close() { public class ConnQueue { private final QueueManager mgr; - private final ConnectionKey address; + private final String peerHost; + private final boolean ssl; + private final int port; + private final String host; + private final ConnectionKey key; private final Queue waiters = new ArrayDeque<>(); private Pool pool; private int connCount; private final int maxSize; final Object metric; - ConnQueue(HttpVersion version, QueueManager mgr, ConnectionKey address) { - this.address = address; + ConnQueue(HttpVersion version, QueueManager mgr, String peerHost, String host, int port, boolean ssl, ConnectionKey key) { + this.key = key; + this.host = host; + this.port = port; + this.ssl = ssl; + this.peerHost = peerHost; this.mgr = mgr; if (version == HttpVersion.HTTP_2) { maxSize = options.getHttp2MaxPoolSize(); @@ -216,7 +219,7 @@ public class ConnQueue { maxSize = options.getMaxPoolSize(); pool = (Pool)new Http1xPool(client, ConnectionManager.this.metrics, options, this, mgr.connectionMap, version, options.getMaxPoolSize()); } - this.metric = ConnectionManager.this.metrics.createEndpoint(address.host, address.port, maxSize); + this.metric = ConnectionManager.this.metrics.createEndpoint(host, port, maxSize); } public synchronized void getConnection(Waiter waiter) { @@ -289,7 +292,7 @@ private void createNewConnection(Waiter waiter) { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(context.nettyEventLoop()); bootstrap.channel(NioSocketChannel.class); - connector.connect(this, bootstrap, context, address.serverName, address.ssl, pool.version(), address.host, address.port, waiter); + connector.connect(this, bootstrap, context, peerHost, ssl, pool.version(), host, port, waiter); } /** @@ -318,9 +321,9 @@ public synchronized void connectionClosed() { createNewConnection(waiter); } else if (connCount == 0) { // No waiters and no connections - remove the ConnQueue - mgr.queueMap.remove(address); + mgr.queueMap.remove(key); if (ConnectionManager.this.metrics.isEnabled()) { - ConnectionManager.this.metrics.closeEndpoint(address.host, address.port, metric); + ConnectionManager.this.metrics.closeEndpoint(host, port, metric); } } } @@ -412,7 +415,7 @@ protected void connect( ConnQueue queue, Bootstrap bootstrap, ContextImpl context, - String serverName, + String peerHost, boolean ssl, HttpVersion version, String host, @@ -435,7 +438,7 @@ protected void connect( ChannelPipeline pipeline = ch.pipeline(); boolean useAlpn = options.isUseAlpn(); if (useAlpn) { - SslHandler sslHandler = new SslHandler(sslHelper.createEngine(client.getVertx(), host, port, serverName)); + SslHandler sslHandler = new SslHandler(sslHelper.createEngine(client.getVertx(), peerHost, port, options.isSni() ? peerHost : null)); ch.pipeline().addLast("ssl", sslHandler); ch.pipeline().addLast(new ApplicationProtocolNegotiationHandler("http/1.1") { @Override @@ -453,7 +456,7 @@ protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { }); } else { if (ssl) { - pipeline.addLast("ssl", new SslHandler(sslHelper.createEngine(vertx, host, port, serverName))); + pipeline.addLast("ssl", new SslHandler(sslHelper.createEngine(vertx, peerHost, port, options.isSni() ? peerHost : null))); } if (version == HttpVersion.HTTP_2) { if (options.isHttp2ClearTextUpgrade()) { diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java index cd7d6319f97..ae99b104193 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java @@ -93,7 +93,7 @@ public class HttpClientImpl implements HttpClient, MetricsProvider { if (uri.getQuery() != null) { requestURI += "?" + uri.getQuery(); } - return Future.succeededFuture(createRequest(null, m, uri.getHost(), port, ssl, requestURI, null)); + return Future.succeededFuture(createRequest(m, uri.getHost(), port, ssl, requestURI, null)); } return null; } catch (Exception e) { @@ -454,12 +454,12 @@ public HttpClientRequest requestAbs(HttpMethod method, String absoluteURI) { port = 443; } } - return createRequest(null, method, url.getHost(), port, ssl, url.getFile(), null); + return createRequest(method, url.getHost(), port, ssl, url.getFile(), null); } @Override public HttpClientRequest request(HttpMethod method, int port, String host, String requestURI) { - return createRequest(null, method, host, port, null, requestURI, null); + return createRequest(method, host, port, null, requestURI, null); } @Override @@ -469,7 +469,7 @@ public HttpClientRequest request(HttpMethod method, RequestOptions options, Hand @Override public HttpClientRequest request(HttpMethod method, RequestOptions options) { - return createRequest(options.getServerName(), method, options.getHost(), options.getPort(), options.isSsl(), options.getURI(), null); + return createRequest(method, options.getHost(), options.getPort(), options.isSsl(), options.getURI(), null); } @Override @@ -904,8 +904,8 @@ boolean isCancelled() { }); } - void getConnectionForRequest(String serverName, boolean ssl, int port, String host, Waiter waiter) { - connectionManager.getConnectionForRequest(serverName, ssl, options.getProtocolVersion(), port, host, waiter); + void getConnectionForRequest(String peerHost, boolean ssl, int port, String host, Waiter waiter) { + connectionManager.getConnectionForRequest(options.getProtocolVersion(), peerHost, ssl, port, host, waiter); } /** @@ -933,11 +933,11 @@ private URL parseUrl(String surl) { } private HttpClient requestNow(HttpMethod method, RequestOptions options, Handler responseHandler) { - createRequest(null, method, options.getHost(), options.getPort(), options.isSsl(), options.getURI(), null).handler(responseHandler).end(); + createRequest(method, options.getHost(), options.getPort(), options.isSsl(), options.getURI(), null).handler(responseHandler).end(); return this; } - private HttpClientRequest createRequest(String serverName, HttpMethod method, String host, int port, Boolean ssl, String relativeURI, MultiMap headers) { + private HttpClientRequest createRequest(HttpMethod method, String host, int port, Boolean ssl, String relativeURI, MultiMap headers) { Objects.requireNonNull(method, "no null method accepted"); Objects.requireNonNull(host, "no null host accepted"); Objects.requireNonNull(relativeURI, "no null relativeURI accepted"); @@ -955,11 +955,11 @@ private HttpClientRequest createRequest(String serverName, HttpMethod method, St headers.add("Proxy-Authorization", "Basic " + Base64.getEncoder() .encodeToString((proxyOptions.getUsername() + ":" + proxyOptions.getPassword()).getBytes())); } - req = new HttpClientRequestImpl(this, useSSL, serverName, method, proxyOptions.getHost(), proxyOptions.getPort(), + req = new HttpClientRequestImpl(this, useSSL, method, proxyOptions.getHost(), proxyOptions.getPort(), relativeURI, vertx); req.setHost(host + (port != 80 ? ":" + port : "")); } else { - req = new HttpClientRequestImpl(this, useSSL, serverName, method, host, port, relativeURI, vertx); + req = new HttpClientRequestImpl(this, useSSL, method, host, port, relativeURI, vertx); } if (headers != null) { req.headers().setAll(headers); diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientRequestImpl.java b/src/main/java/io/vertx/core/http/impl/HttpClientRequestImpl.java index dd39134ee12..b6d9e3a4a90 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientRequestImpl.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientRequestImpl.java @@ -51,7 +51,6 @@ public class HttpClientRequestImpl extends HttpClientRequestBase implements HttpClientRequest { private final VertxInternal vertx; - private final String serverName; private Handler respHandler; private Handler endHandler; private boolean chunked; @@ -77,12 +76,11 @@ public class HttpClientRequestImpl extends HttpClientRequestBase implements Http private long written; private CaseInsensitiveHeaders headers; - HttpClientRequestImpl(HttpClientImpl client, boolean ssl, String serverName, HttpMethod method, String host, int port, + HttpClientRequestImpl(HttpClientImpl client, boolean ssl, HttpMethod method, String host, int port, String relativeURI, VertxInternal vertx) { super(client, ssl, method, host, port, relativeURI); this.chunked = false; this.vertx = vertx; - this.serverName = serverName; } @Override @@ -729,10 +727,22 @@ boolean isCancelled() { } }; + String peerHost; + if (hostHeader != null) { + int idx = hostHeader.lastIndexOf(':'); + if (idx != -1) { + peerHost = hostHeader.substring(0, idx); + } else { + peerHost = hostHeader; + } + } else { + peerHost = host; + } + // We defer actual connection until the first part of body is written or end is called // This gives the user an opportunity to set an exception handler before connecting so // they can capture any exceptions on connection - client.getConnectionForRequest(serverName, ssl, port, host, waiter); + client.getConnectionForRequest(peerHost, ssl, port, host, waiter); connecting = true; } } diff --git a/src/test/java/io/vertx/test/core/Http2ClientTest.java b/src/test/java/io/vertx/test/core/Http2ClientTest.java index 31024f02185..4ab01d0eeee 100644 --- a/src/test/java/io/vertx/test/core/Http2ClientTest.java +++ b/src/test/java/io/vertx/test/core/Http2ClientTest.java @@ -346,7 +346,7 @@ public void testOverrideAuthority() throws Exception { testComplete(); }) .setHost("localhost:4444") - .exceptionHandler(err -> fail()) + .exceptionHandler(this::fail) .end(); await(); } @@ -1374,7 +1374,7 @@ public void testNetSocketConnect() throws Exception { complete(); }); socket.write(Buffer.buffer("some-data")); - }).setHost("whatever.com").sendHead(); + }).sendHead(); await(); } @@ -1431,7 +1431,7 @@ public void testServerCloseNetSocket() throws Exception { complete(); }); socket.write(Buffer.buffer("some-data")); - }).setHost("whatever.com").sendHead(); + }).sendHead(); await(); } diff --git a/src/test/java/io/vertx/test/core/HttpTLSTest.java b/src/test/java/io/vertx/test/core/HttpTLSTest.java index 062cb17ddf7..ea060c00e78 100644 --- a/src/test/java/io/vertx/test/core/HttpTLSTest.java +++ b/src/test/java/io/vertx/test/core/HttpTLSTest.java @@ -408,14 +408,15 @@ public TrustOptions clone() { // Client provides SNI but server ignores it and provides a different cerficate public void testTLSClientIndicatesServerNameIgnoredAndDoesNotTrustServerCert() throws Exception { testTLS(Cert.NONE, Trust.SNI_JKS_HOST1, Cert.SERVER_JKS, Trust.NONE) - .requestOptions(new RequestOptions().setSsl(true).setServerName("host1").setPort(4043).setHost("host1")).fail(); + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("host1")).fail(); } @Test // Client provides SNI but server ignores it and provides a different cerficate - check we get a certificate public void testTLSClientIndicatesServerNameIgnoredAndTrustsServerCert() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SERVER_JKS, Cert.SERVER_JKS, Trust.NONE).clientVerifyHost(false) - .requestOptions(new RequestOptions().setSsl(true).setServerName("host1").setPort(4043).setHost("host1")) + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("host1")) + .clientSni() .pass() .clientPeerCert(); assertEquals("localhost", TestUtils.cnOf(cert)); @@ -425,8 +426,9 @@ public void testTLSClientIndicatesServerNameIgnoredAndTrustsServerCert() throws // Client provides SNI and server responds with a matching certificate for the indicated server name public void testTLSClientIndicatesServerNameAndTrustsServerCert1() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SNI_JKS_HOST1, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() - .requestOptions(new RequestOptions().setSsl(true).setServerName("host1").setPort(4043).setHost("host1")) + .serverSni() + .clientSni() + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("host1")) .pass() .clientPeerCert(); assertEquals("host1", TestUtils.cnOf(cert)); @@ -436,8 +438,9 @@ public void testTLSClientIndicatesServerNameAndTrustsServerCert1() throws Except // Client provides SNI and server responds with a matching certificate for the indicated server name public void testTLSClientIndicatesServerNameAndTrustsServerCert2() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SNI_JKS_HOST2, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() - .requestOptions(new RequestOptions().setSsl(true).setServerName("host2").setPort(4043).setHost("host2")) + .serverSni() + .clientSni() + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("host2")) .pass() .clientPeerCert(); assertEquals("host2", TestUtils.cnOf(cert)); @@ -447,17 +450,19 @@ public void testTLSClientIndicatesServerNameAndTrustsServerCert2() throws Except // Client provides SNI unknown to the server and server responds with the default certificate (first) public void testTLSClientIndicatesServerNameUnknownToServerAndDoesNotTrustServerDefaultCert() throws Exception { testTLS(Cert.NONE, Trust.SNI_JKS_HOST1, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() - .requestOptions(new RequestOptions().setSsl(true).setServerName("unknown").setPort(4043).setHost("unknown")).fail(); + .serverSni() + .clientSni() + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("unknown")).fail(); } @Test // Client provides SNI unknown to the server and server responds with the default certificate (first) public void testTLSClientIndicatesServerNameUnknownToServerAndTrustsServerDefaultCert() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SERVER_JKS, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() + .serverSni() + .clientSni() .clientVerifyHost(false) - .requestOptions(new RequestOptions().setSsl(true).setServerName("unknown").setPort(4043).setHost("unknown")) + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("unknown")) .pass() .clientPeerCert(); assertEquals("localhost", TestUtils.cnOf(cert)); @@ -467,8 +472,9 @@ public void testTLSClientIndicatesServerNameUnknownToServerAndTrustsServerDefaul // Client provides SNI matched on the server by a wildcard certificate public void testTLSClientIndicatesServerNameMatchedByWildcardCertificate() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SNI_JKS_HOST3, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() - .requestOptions(new RequestOptions().setSsl(true).setServerName("sub.host3").setPort(4043).setHost("sub.host3")) + .serverSni() + .clientSni() + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("sub.host3")) .pass() .clientPeerCert(); assertEquals("*.host3", TestUtils.cnOf(cert)); @@ -477,8 +483,9 @@ public void testTLSClientIndicatesServerNameMatchedByWildcardCertificate() throw @Test public void testTLSClientIndicatesServerNameMatchesByCertificateSAN1() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SNI_JKS_HOST4, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() - .requestOptions(new RequestOptions().setSsl(true).setServerName("host4").setPort(4043).setHost("host4")) + .serverSni() + .clientSni() + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("host4")) .pass() .clientPeerCert(); assertEquals("host4 certificate", TestUtils.cnOf(cert)); @@ -487,8 +494,9 @@ public void testTLSClientIndicatesServerNameMatchesByCertificateSAN1() throws Ex @Test public void testTLSClientIndicatesServerNameMatchesByCertificateSAN2() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SNI_JKS_HOST4, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() - .requestOptions(new RequestOptions().setSsl(true).setServerName("www.host4").setPort(4043).setHost("www.host4")) + .serverSni() + .clientSni() + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("www.host4")) .pass() .clientPeerCert(); assertEquals("host4 certificate", TestUtils.cnOf(cert)); @@ -497,8 +505,9 @@ public void testTLSClientIndicatesServerNameMatchesByCertificateSAN2() throws Ex @Test public void testTLSClientIndicatesServerNameMatchesByWildcardCertificateSAN() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SNI_JKS_HOST5, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() - .requestOptions(new RequestOptions().setSsl(true).setServerName("www.host5").setPort(4043).setHost("www.host5")) + .serverSni() + .clientSni() + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("www.host5")) .pass() .clientPeerCert(); assertEquals("host5", TestUtils.cnOf(cert)); @@ -507,8 +516,9 @@ public void testTLSClientIndicatesServerNameMatchesByWildcardCertificateSAN() th @Test public void testTLSClientIndicatesServerNameMatchesByCNWithCertificateSAN1() throws Exception { testTLS(Cert.NONE, Trust.SNI_JKS_HOST5, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() - .requestOptions(new RequestOptions().setSsl(true).setServerName("host5").setPort(4043).setHost("host5")) + .serverSni() + .clientSni() + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("host5")) .fail() .clientPeerCert(); } @@ -516,9 +526,10 @@ public void testTLSClientIndicatesServerNameMatchesByCNWithCertificateSAN1() thr @Test public void testTLSClientIndicatesServerNameMatchesByCNWithCertificateSAN2() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SNI_JKS_HOST5, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() + .serverSni() + .clientSni() .clientVerifyHost(false) - .requestOptions(new RequestOptions().setSsl(true).setServerName("host5").setPort(4043).setHost("host5")) + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("host5")) .pass() .clientPeerCert(); assertEquals("host5", TestUtils.cnOf(cert)); @@ -527,10 +538,23 @@ public void testTLSClientIndicatesServerNameMatchesByCNWithCertificateSAN2() thr @Test public void testTLSClientIndicatesServerNameWithALPN() throws Exception { X509Certificate cert = testTLS(Cert.NONE, Trust.SNI_JKS_HOST1, Cert.SNI_JKS, Trust.NONE) - .serverUsesSni() + .serverSni() + .clientSni() .clientUsesAlpn() .serverUsesAlpn() - .requestOptions(new RequestOptions().setSsl(true).setServerName("host1").setPort(4043).setHost("host1")) + .requestOptions(new RequestOptions().setSsl(true).setPort(4043).setHost("host1")) + .pass() + .clientPeerCert(); + assertEquals("host1", TestUtils.cnOf(cert)); + } + + @Test + // Client provides SNI and server responds with a matching certificate for the indicated server name + public void testTLSClientIndicatesServerNameUsingHostHeader() throws Exception { + X509Certificate cert = testTLS(Cert.NONE, Trust.SNI_JKS_HOST1, Cert.SNI_JKS, Trust.NONE) + .serverSni() + .clientSni() + .requestProvider(client -> client.post(4043, "localhost", "/somepath").setHost("host1:4043")) .pass() .clientPeerCert(); assertEquals("host1", TestUtils.cnOf(cert)); @@ -563,7 +587,8 @@ class TLSTest { String[] serverEnabledSecureTransportProtocol = new String[0]; private String connectHostname; private boolean followRedirects; - private boolean serverUsesSni; + private boolean serverSNI; + private boolean clientSNI; private Function requestProvider = client -> { String httpHost; if (connectHostname != null) { @@ -648,8 +673,13 @@ TLSTest serverEnabledSecureTransportProtocol(String[] value) { return this; } - TLSTest serverUsesSni() { - serverUsesSni = true; + TLSTest serverSni() { + serverSNI = true; + return this; + } + + TLSTest clientSni() { + clientSNI = true; return this; } @@ -725,6 +755,7 @@ TLSTest run(boolean shouldPass) { HttpClientOptions options = new HttpClientOptions(); options.setProtocolVersion(version); options.setSsl(clientSSL); + options.setSni(clientSNI); if (clientTrustAll) { options.setTrustAll(true); } @@ -773,7 +804,7 @@ TLSTest run(boolean shouldPass) { } serverOptions.setUseAlpn(serverUsesAlpn); serverOptions.setSsl(serverSSL); - serverOptions.setSni(serverUsesSni); + serverOptions.setSni(serverSNI); for (String suite: serverEnabledCipherSuites) { serverOptions.addEnabledCipherSuite(suite); } diff --git a/src/test/java/io/vertx/test/core/HttpTest.java b/src/test/java/io/vertx/test/core/HttpTest.java index a128b6ef446..73d85e1460d 100644 --- a/src/test/java/io/vertx/test/core/HttpTest.java +++ b/src/test/java/io/vertx/test/core/HttpTest.java @@ -38,6 +38,7 @@ import io.vertx.core.http.HttpFrame; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.HttpVersion; @@ -3485,24 +3486,27 @@ public void testFollowRedirectPropagatesTimeout() throws Exception { public void testFollowRedirectHost() throws Exception { String scheme = createBaseClientOptions().isSsl() ? "https" : "http"; waitFor(2); - AtomicInteger redirections = new AtomicInteger(); + HttpServerOptions options = createBaseServerOptions(); + int port = options.getPort() + 1; + options.setPort(port); + AtomicInteger redirects = new AtomicInteger(); server.requestHandler(req -> { - switch (redirections.getAndIncrement()) { - case 0: - req.response().setStatusCode(301).putHeader(HttpHeaders.LOCATION, scheme + "://localhost:8080/whatever").end(); - break; - case 1: - assertEquals(scheme + "://the_host/whatever", req.absoluteURI()); - req.response().end(); - complete(); - break; - } + redirects.incrementAndGet(); + req.response().setStatusCode(301).putHeader(HttpHeaders.LOCATION, scheme + "://localhost:" + port + "/whatever").end(); }); startServer(); + HttpServer server2 = vertx.createHttpServer(options); + server2.requestHandler(req -> { + assertEquals(1, redirects.get()); + assertEquals(scheme + "://localhost:" + port + "/whatever", req.absoluteURI()); + req.response().end(); + complete(); + }); + startServer(server2); client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/somepath", resp -> { - assertEquals(scheme + "://the_host/whatever", resp.request().absoluteURI()); + assertEquals(scheme + "://localhost:" + port + "/whatever", resp.request().absoluteURI()); complete(); - }).setFollowRedirects(true).setHost("the_host").end(); + }).setFollowRedirects(true).setHost("localhost:" + options.getPort()).end(); await(); } @@ -3510,34 +3514,37 @@ public void testFollowRedirectHost() throws Exception { public void testFollowRedirectWithCustomHandler() throws Exception { String scheme = createBaseClientOptions().isSsl() ? "https" : "http"; waitFor(2); - AtomicInteger redirections = new AtomicInteger(); + HttpServerOptions options = createBaseServerOptions(); + int port = options.getPort() + 1; + options.setPort(port); + AtomicInteger redirects = new AtomicInteger(); server.requestHandler(req -> { - switch (redirections.getAndIncrement()) { - case 0: - req.response().setStatusCode(301).putHeader(HttpHeaders.LOCATION, "http://localhost:8080/whatever").end(); - break; - case 1: - assertEquals(scheme + "://another_host/custom", req.absoluteURI()); - req.response().end(); - complete(); - break; - } + redirects.incrementAndGet(); + req.response().setStatusCode(301).putHeader(HttpHeaders.LOCATION, "http://localhost:" + port + "/whatever").end(); }); startServer(); + HttpServer server2 = vertx.createHttpServer(options); + server2.requestHandler(req -> { + assertEquals(1, redirects.get()); + assertEquals(scheme + "://localhost:" + port + "/custom", req.absoluteURI()); + req.response().end(); + complete(); + }); + startServer(server2); client.redirectHandler(resp -> { Future fut = Future.future(); vertx.setTimer(25, id -> { - HttpClientRequest req = client.getAbs(scheme + "://localhost:8080/custom"); + HttpClientRequest req = client.getAbs(scheme + "://localhost:" + port + "/custom"); req.putHeader("foo", "foo_another"); - req.setHost("another_host"); + req.setHost("localhost:" + port); fut.complete(req); }); return fut; }); client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/somepath", resp -> { - assertEquals(scheme + "://another_host/custom", resp.request().absoluteURI()); + assertEquals(scheme + "://localhost:" + port + "/custom", resp.request().absoluteURI()); complete(); - }).setFollowRedirects(true).putHeader("foo", "foo_value").setHost("the_host").end(); + }).setFollowRedirects(true).putHeader("foo", "foo_value").setHost("localhost:" + options.getPort()).end(); await(); } diff --git a/src/test/java/io/vertx/test/core/KeyStoreTest.java b/src/test/java/io/vertx/test/core/KeyStoreTest.java index aafe87a8d35..c3a412bbf54 100644 --- a/src/test/java/io/vertx/test/core/KeyStoreTest.java +++ b/src/test/java/io/vertx/test/core/KeyStoreTest.java @@ -16,19 +16,9 @@ package io.vertx.test.core; import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.http.HttpClientRequest; -import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerOptions; -import io.vertx.core.http.RequestOptions; import io.vertx.core.impl.VertxInternal; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; -import io.vertx.core.net.NetClient; -import io.vertx.core.net.NetClientOptions; -import io.vertx.core.net.NetServer; -import io.vertx.core.net.NetServerOptions; import io.vertx.core.net.PemTrustOptions; import io.vertx.core.net.JksOptions; import io.vertx.core.net.PemKeyCertOptions; @@ -411,24 +401,4 @@ public void testFoo() throws Exception { assertNotNull(abc); } */ - - @Test - public void testHttpServerRequest() throws Exception { - KeyCertOptions jksOptions = Cert.SNI_JKS.get(); - JksOptions clientOptions = Trust.SNI_JKS_HOST2.get(); - HttpClient client = vertx.createHttpClient(new HttpClientOptions().setTrustStoreOptions(clientOptions).setSsl(true).setVerifyHost(false)); - HttpServer server = vertx.createHttpServer(new HttpServerOptions().setKeyCertOptions(jksOptions).setSsl(true).setSni(true)); - server.requestHandler(request -> { - request.response().end(); - }); - server.listen(1234, onSuccess(v -> { - HttpClientRequest req = client.get(new RequestOptions().setServerName("host2").setHost("localhost").setPort(1234).setSsl(true)); - req.handler(resp -> { - testComplete(); - }); - req.exceptionHandler(this::fail); - req.end(); - })); - await(); - } }