Skip to content

Commit

Permalink
Support SNI for JNH connector
Browse files Browse the repository at this point in the history
Signed-off-by: jansupol <jan.supol@oracle.com>
  • Loading branch information
jansupol committed Apr 21, 2023
1 parent 71aedfc commit 1059c24
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2023 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 @@ -57,25 +57,34 @@ public class JavaNetHttpClientProperties {
public static final String SSL_PARAMETERS = "jersey.config.jnh.client.sslParameters";

/**
* An instance of the {@link java.net.Authenticator} class that should be used to retrieve
* credentials from a user.
*
* <p>
* An instance of the {@link java.net.Authenticator} class that should be used to retrieve
* credentials from a user.
* </p>
* <p>
* The name of the configuration property is <tt>{@value}</tt>.
* </p>
*/
public static final String PREEMPTIVE_BASIC_AUTHENTICATION =
"jersey.config.jnh.client.preemptiveBasicAuthentication";

/**
* A value of {@code false} indicates the client should handle cookies
* automatically using HttpClient's default cookie policy. A value
* of {@code false} will cause the client to ignore all cookies.
* <p/>
* The value MUST be an instance of {@link java.lang.Boolean}.
* If the property is absent the default value is {@code false}
* <p>
* A value of {@code false} indicates the client should handle cookies
* automatically using HttpClient's default cookie policy. A value
* of {@code false} will cause the client to ignore all cookies.
* </p>
* <p>
* The value MUST be an instance of {@link java.lang.Boolean}.
* If the property is absent the default value is {@code false}
* </p>
* <p>
* The name of the configuration property is <tt>{@value}</tt>.
* </p>
*/
public static final String DISABLE_COOKIES =
"jersey.config.jnh.client.disableCookies";


/**
* Prevent this class from instantiation.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2023 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 @@ -25,6 +25,7 @@
import org.glassfish.jersey.client.ClientResponse;
import org.glassfish.jersey.client.innate.ClientProxy;
import org.glassfish.jersey.client.innate.Expect100ContinueUsage;
import org.glassfish.jersey.client.innate.http.SSLParamConfigurator;
import org.glassfish.jersey.client.spi.AsyncConnectorCallback;
import org.glassfish.jersey.client.spi.Connector;
import org.glassfish.jersey.internal.Version;
Expand All @@ -49,14 +50,12 @@
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;

/**
* Provides a Jersey client {@link Connector}, which internally uses Java's {@link HttpClient}.
Expand Down Expand Up @@ -104,11 +103,14 @@ public JavaNetHttpConnector(final Client client, final Configuration configurati
} else {
httpClientBuilder.followRedirects(HttpClient.Redirect.NORMAL);
}

SSLParameters sslParameters =
getPropertyOrNull(configuration, JavaNetHttpClientProperties.SSL_PARAMETERS, SSLParameters.class);
sslParameters = new SniSslParameters(sslParameters).getSslParameters(client);
if (sslParameters != null) {
httpClientBuilder.sslParameters(sslParameters);
}

final Authenticator preemptiveAuthenticator =
getPropertyOrNull(configuration,
JavaNetHttpClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, Authenticator.class);
Expand Down Expand Up @@ -170,12 +172,18 @@ public OutputStream getOutputStream(int contentLength) throws IOException {
* @return the {@link HttpRequest} instance for the {@link HttpClient} request
*/
private HttpRequest getHttpRequest(ClientRequest request) {
final SSLParamConfigurator sniConfig = SSLParamConfigurator.builder()
.uri(request.getUri())
.configuration(request.getConfiguration())
.build();

final URI sniUri = sniConfig.isSNIRequired() ? sniConfig.toIPRequestUri() : request.getUri();

HttpRequest.Builder builder = HttpRequest.newBuilder();
builder.uri(request.getUri());
builder.uri(sniUri);
HttpRequest.BodyPublisher bodyPublisher = HttpRequest.BodyPublishers.noBody();
if (request.hasEntity()) {
try {
request.enableBuffering();
ByteArrayOutputStreamProvider byteBufferStreamProvider = new ByteArrayOutputStreamProvider();
request.setStreamProvider(byteBufferStreamProvider);
request.writeEntity();
Expand Down Expand Up @@ -296,4 +304,29 @@ public CookieHandler getCookieHandler() {
}
return null;
}

private static class SniSslParameters {
private final SSLParameters sslParameters;

private SniSslParameters(SSLParameters sslParameters) {
this.sslParameters = sslParameters;
}

private SSLParameters getSslParameters(Client client) {
SSLParamConfigurator sniConfig = SSLParamConfigurator.builder()
.configuration(client.getConfiguration())
.build();

if (sniConfig.isSNIRequired()) {
SSLParameters sslParameters = this.sslParameters;
if (sslParameters == null) {
sslParameters = new SSLParameters();
}
sniConfig.setSNIServerName(sslParameters);
return sslParameters;
} else {
return sslParameters;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023 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 @@ -463,6 +463,24 @@ public final class ClientProperties {
*/
public static final String QUERY_PARAM_STYLE = "jersey.config.client.uri.query.param.style";

/**
* <p>
* Most connectors support HOST header value to be used as an SNIHostName. However, the HOST header is restricted in JDK.
* {@code HttpUrlConnector} and {@code JavaNetHttpConnector} need
* to have an extra System Property set to allow HOST header.
* As an option to HOST header, this property allows the HOST name to be pre-set on a Client and does not need to
* be set on each request.
* </p>
* <p>
* The value MUST be an instance of {@link java.lang.String}.
* </p>
* <p>
* The name of the configuration property is <tt>{@value}</tt>.
* </p>
* @since 3.1.2
*/
public static final String SNI_HOST_NAME = "jersey.config.client.sniHostName";

private ClientProperties() {
// prevents instantiation
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,62 @@

package org.glassfish.jersey.client.innate.http;

import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.HttpHeaders;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.ClientRequest;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import jakarta.ws.rs.core.UriBuilder;
import org.glassfish.jersey.internal.PropertiesResolver;
import org.glassfish.jersey.internal.util.PropertiesHelper;

import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;

/**
* A unified routines to configure {@link SSLParameters}.
* To be reused in connectors.
*/
public final class SSLParamConfigurator {
private final URI uri;
private final Map<String, List<Object>> httpHeaders;
private final Optional<SniConfigurator> sniConfigurator;

/**
* Builder of the {@link SSLParamConfigurator} instance.
*/
public static final class Builder {
private ClientRequest clientRequest;
private URI uri;
private Map<String, List<Object>> httpHeaders;
private URI uri = null;
private String sniHostNameHeader = null;
private String sniHostNameProperty = null;
private boolean setAlways = false;

/**
* Sets the {@link ClientRequest} instance.
* Sets the SNIHostName and {@link URI} from the {@link ClientRequest} instance.
* @param clientRequest the {@link ClientRequest}
* @return the builder instance
*/
public Builder request(ClientRequest clientRequest) {
this.clientRequest = clientRequest;
this.httpHeaders = null;
this.uri = null;
this.sniHostNameHeader = getSniHostNameHeader(clientRequest.getHeaders());
this.sniHostNameProperty = clientRequest.resolveProperty(ClientProperties.SNI_HOST_NAME, String.class);
this.uri = clientRequest.getUri();
return this;
}

/**
* Sets the SNIHostName from the {@link Configuration} instance.
* @param configuration the {@link Configuration}
* @return the builder instance
*/
public Builder configuration(Configuration configuration) {
this.sniHostNameProperty = (String) configuration.getProperty(ClientProperties.SNI_HOST_NAME);
return this;
}

Expand All @@ -65,7 +81,6 @@ public Builder request(ClientRequest clientRequest) {
* @return the builder instance
*/
public Builder uri(URI uri) {
this.clientRequest = null;
this.uri = uri;
return this;
}
Expand All @@ -76,8 +91,7 @@ public Builder uri(URI uri) {
* @return the builder instance
*/
public Builder headers(Map<String, List<Object>> httpHeaders) {
this.clientRequest = null;
this.httpHeaders = httpHeaders;
this.sniHostNameHeader = getSniHostNameHeader(httpHeaders);
return this;
}

Expand All @@ -99,12 +113,31 @@ public Builder setSNIAlways(boolean setAlways) {
public SSLParamConfigurator build() {
return new SSLParamConfigurator(this);
}

private static String getSniHostNameHeader(Map<String, List<Object>> httpHeaders) {
List<Object> hostHeaders = httpHeaders.get(HttpHeaders.HOST);
if (hostHeaders == null || hostHeaders.get(0) == null) {
return null;
}

final String hostHeader = hostHeaders.get(0).toString();
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;
} else {
trimmedHeader = null;
}

return trimmedHeader;
}
}

private SSLParamConfigurator(SSLParamConfigurator.Builder builder) {
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);
String sniHostName = builder.sniHostNameHeader == null ? builder.sniHostNameProperty : builder.sniHostNameHeader;
uri = builder.uri;
sniConfigurator = SniConfigurator.createWhenHostHeader(uri, sniHostName, builder.setAlways);
}

/**
Expand Down Expand Up @@ -177,6 +210,15 @@ public void setSNIServerName(SSLSocket sslSocket) {
sniConfigurator.ifPresent(sni -> sni.setServerNames(sslSocket));
}

/**
* Set {@link javax.net.ssl.SNIServerName} for the {@link SSLParameters} when SNI should be used
* (i.e. {@link jakarta.ws.rs.core.HttpHeaders#HOST} differs from HTTP request host name)
* @param parameters the {@link SSLParameters} to be set
*/
public void setSNIServerName(SSLParameters parameters) {
sniConfigurator.ifPresent(sni -> sni.updateSSLParameters(parameters));
}

/**
* Set setEndpointIdentificationAlgorithm to HTTPS. This is to prevent man-in-the-middle attacks.
* @param sslEngine the {@link SSLEngine} the algorithm is set for.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
Expand All @@ -49,32 +48,24 @@ String getHostName() {
/**
* Create ClientSNI 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 sniHostName the SniHostName either from HttpHeaders or the
* {@link org.glassfish.jersey.client.ClientProperties#SNI_HOST_NAME} property from Configuration object.
* @param whenDiffer create {@SniConfigurator only when different from the request URI host}
* @return ClientSNI or empty when {@link HttpHeaders#HOST}
*/
static Optional<SniConfigurator> createWhenHostHeader(URI hostUri, Map<String, List<Object>> headers, boolean whenDiffer) {
List<Object> hostHeaders = headers.get(HttpHeaders.HOST);
if (hostHeaders == null || hostHeaders.get(0) == null) {
static Optional<SniConfigurator> createWhenHostHeader(URI hostUri, String sniHostName, boolean whenDiffer) {
if (sniHostName == null) {
return Optional.empty();
}

final String hostHeader = hostHeaders.get(0).toString();
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;
} else {
return Optional.empty();
}

final String hostUriString = hostUri.getHost();
if (!whenDiffer && hostUriString.equals(trimmedHeader)) {
return Optional.empty();
if (hostUri != null) {
final String hostUriString = hostUri.getHost();
if (!whenDiffer && hostUriString.equals(sniHostName)) {
return Optional.empty();
}
}

return Optional.of(new SniConfigurator(trimmedHeader));
return Optional.of(new SniConfigurator(sniHostName));
}

/**
Expand All @@ -97,7 +88,7 @@ void setServerNames(SSLSocket sslSocket) {
sslSocket.setSSLParameters(sslParameters);
}

private SSLParameters updateSSLParameters(SSLParameters sslParameters) {
SSLParameters updateSSLParameters(SSLParameters sslParameters) {
SNIHostName serverName = new SNIHostName(hostName);
List<SNIServerName> serverNames = new LinkedList<>();
serverNames.add(serverName);
Expand Down
Loading

0 comments on commit 1059c24

Please sign in to comment.