Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for overriding the SNIHostName or turn it off. Allow for Domain Fronting. #5617

Merged
merged 1 commit into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -60,8 +60,10 @@ class ConnectorConfiguration {
private final int connectTimeout;
private final ProxyConfiguration proxyConfiguration;
private final AtomicReference<SSLParamConfigurator> sniConfigs = new AtomicReference<>(null);
private final Configuration configuration;

ConnectorConfiguration(Client client, Configuration config) {
configuration = config;
final Map<String, Object> properties = config.getProperties();

int proposedChunkSize = JdkConnectorProperties.getValue(properties, ClientProperties.CHUNKED_ENCODING_SIZE,
Expand Down Expand Up @@ -181,6 +183,10 @@ SSLParamConfigurator getSniConfig() {
return sniConfigs.get();
}

Configuration getConfiguration() {
return configuration;
}

@Override
public String toString() {
return "ConnectorConfiguration{"
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -51,7 +51,8 @@ class HttpConnectionPool {
void send(HttpRequest httpRequest, CompletionHandler<HttpResponse> completionHandler) {
final Map<String, List<Object>> 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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -207,7 +207,7 @@ protected void execute(final ClientRequest jerseyRequest, final Set<URI> 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<Channel> conns;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -482,6 +482,20 @@ public final class ClientProperties {
*/
public static final String CONNECTOR_PROVIDER = "jersey.config.client.connector.provider";

/**
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* @since 2.43
*/
public static final String SNI_HOST_NAME = "jersey.config.client.snihostname";

/**
* <p>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()}.
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -35,7 +38,6 @@
*/
public final class SSLParamConfigurator {
private final URI uri;
private final Map<String, List<Object>> httpHeaders;
private final Optional<SniConfigurator> sniConfigurator;

/**
Expand All @@ -46,6 +48,7 @@ public static final class Builder {
private URI uri;
private Map<String, List<Object>> httpHeaders;
private boolean setAlways = false;
private String sniHostPrecedence = null;

/**
* Sets the {@link ClientRequest} instance.
Expand Down Expand Up @@ -92,6 +95,62 @@ public Builder setSNIAlways(boolean setAlways) {
return this;
}

/**
* <p>
* Sets the {@code hostName} to be used for calculating the {@link javax.net.ssl.SNIHostName}.
* Takes precedence over the HTTP HOST header, if set.
* </p>
* <p>
* 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.
* </p>
* @param hostName the host the {@code SNIHostName} should be set for.
* @return the builder instance.
*/
public Builder setSNIHostName(String hostName) {
sniHostPrecedence = hostName;
return this;
}

/**
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* @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));
}

/**
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* @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.
Expand All @@ -102,9 +161,15 @@ public SSLParamConfigurator build() {
}

private SSLParamConfigurator(SSLParamConfigurator.Builder builder) {
final Map<String, List<Object>> 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);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<SniConfigurator> createWhenHostHeader(URI hostUri, Map<String, List<Object>> headers, boolean whenDiffer) {
List<Object> hostHeaders = headers.get(HttpHeaders.HOST);
Expand All @@ -64,11 +65,23 @@ static Optional<SniConfigurator> createWhenHostHeader(URI hostUri, Map<String, L
}

final String hostHeader = hostHeaders.get(0).toString();
return createWhenHostHeader(hostUri, hostHeader, whenDiffer);
}

/**
* Create {@link SniConfigurator} when {@code sniHost} is set different from the request URI host
* (or {@code whenDiffer}.is false).
* @param hostUri the Uri of the HTTP request
* @param sniHost the preferred host name to create the {@link SNIHostName}
* @param whenDiffer create {@SniConfigurator only when different from the request URI host}
* @return Optional {@link SniConfigurator} or empty when {@code sniHost} is equal to the requestHost
*/
static Optional<SniConfigurator> 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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@ public SSLSocketFactory get() {
private ClientResponse _apply(final ClientRequest request) throws IOException {
final HttpURLConnection uc;
final Optional<ClientProxy> 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();
Expand Down
19 changes: 17 additions & 2 deletions docs/src/main/docbook/appendix-properties.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<!--

Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2013, 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
Expand Down Expand Up @@ -1062,7 +1062,22 @@
</para>
</entry>
</row>

<row>
<entry>&jersey.client.ClientProperties.SNI_HOST_NAME; (Jersey 2.43 or later)</entry>
<entry><literal>jersey.config.client.snihostname</literal></entry>
<entry>
<para>
Sets the host name to be used for calculating the <literal>javax.net.ssl.SNIHostName</literal>
during the HTTPS request. Takes precedence over the HTTP HOST header, if set.
</para>
<para>
By default, the <literal>SNIHostName</literal> is set when the HOST HTTP header differs from
the HTTP request host. When the property value host name matches the HTTPS request host,
the <literal>SNIHostName</literal> is not set, and the HTTP HOST header is not used for
setting the <literal>SNIHostName</literal>. This allows for Domain Fronting.
</para>
</entry>
</row>
<row>
<entry>&jersey.client.ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION;
(Jersey 2.2 or later)</entry>
Expand Down
15 changes: 14 additions & 1 deletion docs/src/main/docbook/client.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<!--

Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2010, 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
Expand Down Expand Up @@ -1118,6 +1118,19 @@ Client client = ClientBuilder.newBuilder().sslContext(sslContext).build();</prog
Note that only <literal>Apache Connector, JDK Connector, Netty connector</literal>, and the default
<literal>HttpUrlConnector</literal> do support this feature.
</para>
<para>
Sometimes, it may be required the <literal>SNIHostName</literal> is not set, or it differs from the
HTTP <literal>Host</literal> header. In that case, the &jersey.client.ClientProperties.SNI_HOST_NAME;
property can be utilized. The property sets the host name to be used for calculating the
<literal>javax.net.ssl.SNIHostName</literal>. The property takes precedence over the HTTP
<literal>Host</literal> header.
</para>
<para>
When the host name in the the &jersey.client.ClientProperties.SNI_HOST_NAME; property matches the HTTP request
host, the <literal>SNIHostName</literal> is not set, and the HTTP <literal>Host</literal> header is not used
for setting the <literal>SNIHostName</literal>.
Turning the SNI off allows for Domain Fronting.
</para>
</section>

</section>
Expand Down
Loading