From 65820663c9457fd0cedb1cf6df9c29584649b72e Mon Sep 17 00:00:00 2001 From: jansupol Date: Thu, 18 Apr 2024 21:28:36 +0200 Subject: [PATCH] Allow for overriding the SNIHostName or turn it off. Allow for Domain Fronting. Signed-off-by: jansupol --- .../apache/connector/ApacheConnector.java | 3 +- .../apache5/connector/Apache5Connector.java | 3 +- .../internal/ConnectorConfiguration.java | 8 +- .../internal/HttpConnectionPool.java | 5 +- .../netty/connector/NettyConnector.java | 4 +- .../jersey/client/ClientProperties.java | 16 +++- .../innate/http/SSLParamConfigurator.java | 73 ++++++++++++++++++- .../client/innate/http/SniConfigurator.java | 27 +++++-- .../client/internal/HttpUrlConnector.java | 3 +- docs/src/main/docbook/appendix-properties.xml | 19 ++++- docs/src/main/docbook/client.xml | 15 +++- docs/src/main/docbook/jersey.ent | 3 +- .../jersey/tests/e2e/tls/SniTest.java | 30 ++++++-- 13 files changed, 180 insertions(+), 29 deletions(-) diff --git a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java index 93f8aba948..e4c77db653 100644 --- a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java +++ b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java @@ -882,7 +882,8 @@ protected void prepareSocket(SSLSocket socket) throws IOException { Object objectRequest = context.getAttribute(JERSEY_REQUEST_ATTR_NAME); if (objectRequest != null) { ClientRequest clientRequest = (ClientRequest) objectRequest; - SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(clientRequest).build(); + SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(clientRequest) + .setSNIHostName(clientRequest).build(); sniConfig.setSNIServerName(socket); } } diff --git a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java index 92fb44becc..95154bd70e 100644 --- a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java +++ b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java @@ -881,7 +881,8 @@ protected void prepareSocket(SSLSocket socket) throws IOException { Object objectRequest = context.getAttribute(JERSEY_REQUEST_ATTR_NAME); if (objectRequest != null) { ClientRequest clientRequest = (ClientRequest) objectRequest; - SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(clientRequest).build(); + SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(clientRequest) + .setSNIHostName(clientRequest).build(); sniConfig.setSNIServerName(socket); final int socketTimeout = ((ClientRequest) objectRequest).resolveProperty(ClientProperties.READ_TIMEOUT, -1); diff --git a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/ConnectorConfiguration.java b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/ConnectorConfiguration.java index afb0c6cb3e..687688f143 100644 --- a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/ConnectorConfiguration.java +++ b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/ConnectorConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -60,8 +60,10 @@ class ConnectorConfiguration { private final int connectTimeout; private final ProxyConfiguration proxyConfiguration; private final AtomicReference sniConfigs = new AtomicReference<>(null); + private final Configuration configuration; ConnectorConfiguration(Client client, Configuration config) { + configuration = config; final Map properties = config.getProperties(); int proposedChunkSize = JdkConnectorProperties.getValue(properties, ClientProperties.CHUNKED_ENCODING_SIZE, @@ -181,6 +183,10 @@ SSLParamConfigurator getSniConfig() { return sniConfigs.get(); } + Configuration getConfiguration() { + return configuration; + } + @Override public String toString() { return "ConnectorConfiguration{" diff --git a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpConnectionPool.java b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpConnectionPool.java index b20f00cad2..7413caf34b 100644 --- a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpConnectionPool.java +++ b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpConnectionPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -51,7 +51,8 @@ class HttpConnectionPool { void send(HttpRequest httpRequest, CompletionHandler completionHandler) { final Map> headers = new HashMap<>(); httpRequest.getHeaders().forEach((k, v) -> headers.put(k, (List) v)); - final SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().uri(httpRequest.getUri()).headers(headers).build(); + final SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().uri(httpRequest.getUri()) + .headers(headers).setSNIHostName(connectorConfiguration.getConfiguration()).build(); connectorConfiguration.setSniConfig(sniConfig); final DestinationConnectionPool.DestinationKey destinationKey = new DestinationConnectionPool.DestinationKey( diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java index dba784ca8c..1a548dcb26 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -207,7 +207,7 @@ protected void execute(final ClientRequest jerseyRequest, final Set redirec try { final SSLParamConfigurator sslConfig = SSLParamConfigurator.builder() - .request(jerseyRequest).setSNIAlways(true).build(); + .request(jerseyRequest).setSNIAlways(true).setSNIHostName(jerseyRequest).build(); String key = requestUri.getScheme() + "://" + sslConfig.getSNIHostName() + ":" + port; ArrayList conns; diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java index 7ba912a1d0..5ea9a6d96e 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -482,6 +482,20 @@ public final class ClientProperties { */ public static final String CONNECTOR_PROVIDER = "jersey.config.client.connector.provider"; + /** + *

+ * Sets the {@code hostName} to be used for calculating the {@link javax.net.ssl.SNIHostName} during the HTTPS request. + * Takes precedence over the HTTP HOST header, if set. + *

+ *

+ * By default, the {@code SNIHostName} is set when the HOST HTTP header differs from the HTTP request host. + * When the {@code hostName} matches the HTTPS request host, the {@code SNIHostName} is not set, + * and the HTTP HOST header is not used for setting the {@code SNIHostName}. This allows for Domain Fronting. + *

+ * @since 2.43 + */ + public static final String SNI_HOST_NAME = "jersey.config.client.snihostname"; + /** *

The {@link javax.net.ssl.SSLContext} {@link java.util.function.Supplier} to be used to set ssl context in the current * HTTP request. Has precedence over the {@link Client#getSslContext()}. diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java index edfe29bb89..8c9607bc03 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,11 +16,14 @@ package org.glassfish.jersey.client.innate.http; +import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.internal.PropertiesResolver; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; +import javax.ws.rs.core.Configuration; import javax.ws.rs.core.UriBuilder; import java.net.InetAddress; import java.net.URI; @@ -35,7 +38,6 @@ */ public final class SSLParamConfigurator { private final URI uri; - private final Map> httpHeaders; private final Optional sniConfigurator; /** @@ -46,6 +48,7 @@ public static final class Builder { private URI uri; private Map> httpHeaders; private boolean setAlways = false; + private String sniHostPrecedence = null; /** * Sets the {@link ClientRequest} instance. @@ -92,6 +95,62 @@ public Builder setSNIAlways(boolean setAlways) { return this; } + /** + *

+ * Sets the {@code hostName} to be used for calculating the {@link javax.net.ssl.SNIHostName}. + * Takes precedence over the HTTP HOST header, if set. + *

+ *

+ * By default, the {@code SNIHostName} is set when the HOST HTTP header differs from the HTTP request host. + * When the {@code hostName} matches the HTTP request host, the {@code SNIHostName} is not set, + * and the HTTP HOST header is not used for setting the {@code SNIHostName}. This allows Domain Fronting. + *

+ * @param hostName the host the {@code SNIHostName} should be set for. + * @return the builder instance. + */ + public Builder setSNIHostName(String hostName) { + sniHostPrecedence = hostName; + return this; + } + + /** + *

+ * Sets the {@code hostName} to be used for calculating the {@link javax.net.ssl.SNIHostName}. + * The {@code hostName} value is taken from the {@link Configuration} if the property + * {@link ClientProperties#SNI_HOST_NAME} is set. + * Takes precedence over the HTTP HOST header, if set. + *

+ *

+ * By default, the {@code SNIHostName} is set when the HOST HTTP header differs from the HTTP request host. + * When the {@code hostName} matches the HTTP request host, the {@code SNIHostName} is not set, + * and the HTTP HOST header is not used for setting the {@code SNIHostName}. This allows for Domain Fronting. + *

+ * @param configuration the host the {@code SNIHostName} should be set for. + * @return the builder instance. + */ + public Builder setSNIHostName(Configuration configuration) { + return setSNIHostName((String) configuration.getProperty(ClientProperties.SNI_HOST_NAME)); + } + + /** + *

+ * Sets the {@code hostName} to be used for calculating the {@link javax.net.ssl.SNIHostName}. + * The {@code hostName} value is taken from the {@link PropertiesResolver} if the property + * {@link ClientProperties#SNI_HOST_NAME} is set. + * Takes precedence over the HTTP HOST header, if set. + *

+ *

+ * By default, the {@code SNIHostName} is set when the HOST HTTP header differs from the HTTP request host. + * When the {@code hostName} matches the HTTPS request host, the {@code SNIHostName} is not set, + * and the HTTP HOST header is not used for setting the {@code SNIHostName}. This allows for Domain Fronting. + *

+ * @param resolver the host the {@code SNIHostName} should be set for. + * @return the builder instance. + */ + public Builder setSNIHostName(PropertiesResolver resolver) { + return setSNIHostName(resolver.resolveProperty(ClientProperties.SNI_HOST_NAME, String.class)); + } + /** * Builds the {@link SSLParamConfigurator} instance. * @return the configured {@link SSLParamConfigurator} instance. @@ -102,9 +161,15 @@ public SSLParamConfigurator build() { } private SSLParamConfigurator(SSLParamConfigurator.Builder builder) { + final Map> httpHeaders = + builder.clientRequest != null ? builder.clientRequest.getHeaders() : builder.httpHeaders; this.uri = builder.clientRequest != null ? builder.clientRequest.getUri() : builder.uri; - this.httpHeaders = builder.clientRequest != null ? builder.clientRequest.getHeaders() : builder.httpHeaders; - sniConfigurator = SniConfigurator.createWhenHostHeader(uri, httpHeaders, builder.setAlways); + if (builder.sniHostPrecedence == null) { + sniConfigurator = SniConfigurator.createWhenHostHeader(uri, httpHeaders, builder.setAlways); + } else { + // Do not set SNI always, the property can be used to turn the SNI off + sniConfigurator = SniConfigurator.createWhenHostHeader(uri, builder.sniHostPrecedence, false); + } } /** diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java index ae484ef986..403ce48e99 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -51,11 +51,12 @@ String getHostName() { } /** - * Create ClientSNI when {@link HttpHeaders#HOST} is set different from the request URI host (or {@code whenDiffer}.is false). + * Create {@link SniConfigurator} when {@link HttpHeaders#HOST} is set different from the request URI host + * (or {@code whenDiffer}.is false). * @param hostUri the Uri of the HTTP request * @param headers the HttpHeaders * @param whenDiffer create {@SniConfigurator only when different from the request URI host} - * @return ClientSNI or empty when {@link HttpHeaders#HOST} + * @return Optional {@link SniConfigurator} or empty when {@link HttpHeaders#HOST} is equal to the requestHost */ static Optional createWhenHostHeader(URI hostUri, Map> headers, boolean whenDiffer) { List hostHeaders = headers.get(HttpHeaders.HOST); @@ -64,11 +65,23 @@ static Optional createWhenHostHeader(URI hostUri, Map createWhenHostHeader(URI hostUri, String sniHost, boolean whenDiffer) { final String trimmedHeader; - if (hostHeader != null) { - int index = hostHeader.indexOf(':'); // RFC 7230 Host = uri-host [ ":" port ] ; - final String trimmedHeader0 = index != -1 ? hostHeader.substring(0, index).trim() : hostHeader.trim(); - trimmedHeader = trimmedHeader0.isEmpty() ? hostHeader : trimmedHeader0; + if (sniHost != null) { + int index = sniHost.indexOf(':'); // RFC 7230 Host = uri-host [ ":" port ] ; + final String trimmedHeader0 = index != -1 ? sniHost.substring(0, index).trim() : sniHost.trim(); + trimmedHeader = trimmedHeader0.isEmpty() ? sniHost : trimmedHeader0; } else { return Optional.empty(); } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java index ebe11845f9..443349631d 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java @@ -357,7 +357,8 @@ public SSLSocketFactory get() { private ClientResponse _apply(final ClientRequest request) throws IOException { final HttpURLConnection uc; final Optional proxy = ClientProxy.proxyFromRequest(request); - final SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(request).build(); + final SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(request) + .setSNIHostName(request).build(); final URI sniUri; if (sniConfig.isSNIRequired()) { sniUri = sniConfig.toIPRequestUri(); diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml index 80529206cc..f1d6140e78 100644 --- a/docs/src/main/docbook/appendix-properties.xml +++ b/docs/src/main/docbook/appendix-properties.xml @@ -1,7 +1,7 @@