Skip to content

Commit

Permalink
Add SNI Support based on Host header
Browse files Browse the repository at this point in the history
Signed-off-by: jansupol <[email protected]>
  • Loading branch information
jansupol committed Jan 26, 2023
1 parent cf432fa commit a89d668
Show file tree
Hide file tree
Showing 22 changed files with 1,823 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 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 @@ -24,6 +24,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedList;
Expand All @@ -37,6 +38,7 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;

import javax.net.ssl.SSLSocket;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Configuration;
Expand All @@ -53,6 +55,7 @@
import org.glassfish.jersey.client.ClientResponse;
import org.glassfish.jersey.client.RequestEntityProcessing;
import org.glassfish.jersey.client.innate.ClientProxy;
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.util.PropertiesHelper;
Expand Down Expand Up @@ -103,6 +106,7 @@
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.io.ChunkedOutputStream;
import org.apache.http.io.SessionOutputBuffer;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.TextUtils;
import org.apache.http.util.VersionInfo;

Expand Down Expand Up @@ -180,6 +184,7 @@
class ApacheConnector implements Connector {

private static final Logger LOGGER = Logger.getLogger(ApacheConnector.class.getName());
private static final String JERSEY_REQUEST_ATTR_NAME = "JerseyRequestAttribute";
private static final VersionInfo vi;
private static final String release;

Expand Down Expand Up @@ -381,15 +386,15 @@ private HttpClientConnectionManager createConnectionManager(

final LayeredConnectionSocketFactory sslSocketFactory;
if (sslContext != null) {
sslSocketFactory = new SSLConnectionSocketFactory(
sslSocketFactory = new SniSSLConnectionSocketFactory(
sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
} else {
if (useSystemProperties) {
sslSocketFactory = new SSLConnectionSocketFactory(
sslSocketFactory = new SniSSLConnectionSocketFactory(
(SSLSocketFactory) SSLSocketFactory.getDefault(),
supportedProtocols, supportedCipherSuites, hostnameVerifier);
} else {
sslSocketFactory = new SSLConnectionSocketFactory(
sslSocketFactory = new SniSSLConnectionSocketFactory(
SSLContexts.createDefault(),
hostnameVerifier);
}
Expand Down Expand Up @@ -450,14 +455,16 @@ public CookieStore getCookieStore() {
public ClientResponse apply(final ClientRequest clientRequest) throws ProcessingException {
final HttpUriRequest request = getUriHttpRequest(clientRequest);
final Map<String, String> clientHeadersSnapshot = writeOutBoundHeaders(clientRequest, request);
final HttpHost httpHost = getHost(request);

try {
final CloseableHttpResponse response;
final HttpClientContext context = HttpClientContext.create();

if (preemptiveBasicAuth) {
final AuthCache authCache = new BasicAuthCache();
final BasicScheme basicScheme = new BasicScheme();
authCache.put(getHost(request), basicScheme);
authCache.put(httpHost, basicScheme);
context.setAuthCache(authCache);
}

Expand All @@ -468,7 +475,8 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
context.setCredentialsProvider(credentialsProvider);
}

response = client.execute(getHost(request), request, context);
context.setAttribute(JERSEY_REQUEST_ATTR_NAME, clientRequest);
response = client.execute(httpHost, request, context);
HeaderUtils.checkHeaderChanges(clientHeadersSnapshot, clientRequest.getHeaders(),
this.getClass().getName(), clientRequest.getConfiguration());

Expand Down Expand Up @@ -821,4 +829,56 @@ protected OutputStream createOutputStream(final long len, final SessionOutputBuf
return super.createOutputStream(len, outbuffer);
}
}

private static final class SniSSLConnectionSocketFactory extends SSLConnectionSocketFactory {

private final ThreadLocal<HttpContext> httpContexts = new ThreadLocal<>();

public SniSSLConnectionSocketFactory(final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final HostnameVerifier hostnameVerifier) {
super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}

public SniSSLConnectionSocketFactory(final javax.net.ssl.SSLSocketFactory socketFactory,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final HostnameVerifier hostnameVerifier) {
super(socketFactory, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}

public SniSSLConnectionSocketFactory(
final SSLContext sslContext, final HostnameVerifier hostnameVerifier) {
super(sslContext, hostnameVerifier);
}

@Override
public Socket createLayeredSocket(
final Socket socket,
final String target,
final int port,
final HttpContext context) throws IOException {
httpContexts.set(context);
try {
return super.createLayeredSocket(socket, target, port, context);
} finally {
httpContexts.remove();
}
}

@Override
protected void prepareSocket(SSLSocket socket) throws IOException {
HttpContext context = httpContexts.get();

if (context != null) {
Object objectRequest = context.getAttribute(JERSEY_REQUEST_ATTR_NAME);
if (objectRequest != null) {
ClientRequest clientRequest = (ClientRequest) objectRequest;
SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(clientRequest).build();
sniConfig.setSNIServerName(socket);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 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 @@ -24,6 +24,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
Expand All @@ -37,6 +38,7 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;

import javax.net.ssl.SSLSocket;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Configuration;
Expand All @@ -48,6 +50,7 @@
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

import org.apache.hc.client5.http.AuthenticationStrategy;
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
import org.apache.hc.client5.http.auth.AuthCache;
Expand All @@ -65,6 +68,7 @@
import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.auth.BasicScheme;
import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
Expand All @@ -86,6 +90,7 @@
import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
import org.apache.hc.core5.http.io.entity.BufferedHttpEntity;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.TextUtils;
import org.apache.hc.core5.util.Timeout;
Expand All @@ -95,6 +100,7 @@
import org.glassfish.jersey.client.ClientResponse;
import org.glassfish.jersey.client.RequestEntityProcessing;
import org.glassfish.jersey.client.innate.ClientProxy;
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.util.PropertiesHelper;
Expand Down Expand Up @@ -176,6 +182,7 @@
class Apache5Connector implements Connector {

private static final Logger LOGGER = Logger.getLogger(Apache5Connector.class.getName());
private static final String JERSEY_REQUEST_ATTR_NAME = "JerseyRequestAttribute";
private static final VersionInfo vi;
private static final String release;

Expand Down Expand Up @@ -385,15 +392,15 @@ private HttpClientConnectionManager createConnectionManager(

final LayeredConnectionSocketFactory sslSocketFactory;
if (sslContext != null) {
sslSocketFactory = new SSLConnectionSocketFactory(
sslSocketFactory = new SniSSLConnectionSocketFactory(
sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
} else {
if (useSystemProperties) {
sslSocketFactory = new SSLConnectionSocketFactory(
sslSocketFactory = new SniSSLConnectionSocketFactory(
(SSLSocketFactory) SSLSocketFactory.getDefault(),
supportedProtocols, supportedCipherSuites, hostnameVerifier);
} else {
sslSocketFactory = new SSLConnectionSocketFactory(
sslSocketFactory = new SniSSLConnectionSocketFactory(
SSLContexts.createDefault(),
hostnameVerifier);
}
Expand Down Expand Up @@ -458,12 +465,7 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
try {
final CloseableHttpResponse response;
final HttpClientContext context = HttpClientContext.create();
if (preemptiveBasicAuth) {
final AuthCache authCache = new BasicAuthCache();
final BasicScheme basicScheme = new BasicScheme();
authCache.put(getHost(request), basicScheme);
context.setAuthCache(authCache);
}
final HttpHost httpHost = getHost(request);

// If a request-specific CredentialsProvider exists, use it instead of the default one
CredentialsProvider credentialsProvider =
Expand All @@ -472,7 +474,18 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing
context.setCredentialsProvider(credentialsProvider);
}

response = client.execute(getHost(request), request, context);
if (preemptiveBasicAuth) {
final AuthCache authCache = new BasicAuthCache();
final BasicScheme basicScheme = new BasicScheme();
final AuthScope authScope = new AuthScope(httpHost);
basicScheme.initPreemptive(credentialsProvider.getCredentials(authScope, context));
context.resetAuthExchange(httpHost, basicScheme);
authCache.put(httpHost, basicScheme); // must be after initPreemptive
context.setAuthCache(authCache);
}

context.setAttribute(JERSEY_REQUEST_ATTR_NAME, clientRequest);
response = client.execute(httpHost, request, context);
HeaderUtils.checkHeaderChanges(clientHeadersSnapshot, clientRequest.getHeaders(),
this.getClass().getName(), clientRequest.getConfiguration());

Expand Down Expand Up @@ -798,4 +811,77 @@ private ConnectionFactory(final int chunkSize) {
);
}
}

private static final class SniSSLConnectionSocketFactory extends SSLConnectionSocketFactory {

private final ThreadLocal<HttpContext> httpContexts = new ThreadLocal<>();

public SniSSLConnectionSocketFactory(final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final HostnameVerifier hostnameVerifier) {
super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}

public SniSSLConnectionSocketFactory(final javax.net.ssl.SSLSocketFactory socketFactory,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final HostnameVerifier hostnameVerifier) {
super(socketFactory, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}

public SniSSLConnectionSocketFactory(
final SSLContext sslContext, final HostnameVerifier hostnameVerifier) {
super(sslContext, hostnameVerifier);
}

/* Pre 5.2 */
@Override
public Socket createLayeredSocket(
final Socket socket,
final String target,
final int port,
final HttpContext context) throws IOException {
httpContexts.set(context);
try {
return super.createLayeredSocket(socket, target, port, context);
} finally {
httpContexts.remove();
}
}

/* Post 5.2 */
public Socket createLayeredSocket(
final Socket socket,
final String target,
final int port,
final Object attachment,
final HttpContext context) throws IOException {
httpContexts.set(context);
try {
return super.createLayeredSocket(socket, target, port, attachment, context);
} finally {
httpContexts.remove();
}
}

@Override
protected void prepareSocket(SSLSocket socket) throws IOException {
HttpContext context = httpContexts.get();

if (context != null) {
Object objectRequest = context.getAttribute(JERSEY_REQUEST_ATTR_NAME);
if (objectRequest != null) {
ClientRequest clientRequest = (ClientRequest) objectRequest;
SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(clientRequest).build();
sniConfig.setSNIServerName(socket);

final int socketTimeout = ((ClientRequest) objectRequest).resolveProperty(ClientProperties.READ_TIMEOUT, -1);
if (socketTimeout >= 0) {
socket.setSoTimeout(socketTimeout);
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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 @@ -19,6 +19,7 @@
import java.net.CookiePolicy;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -30,6 +31,7 @@

import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.innate.http.SSLParamConfigurator;
import org.glassfish.jersey.jdk.connector.JdkConnectorProperties;

/**
Expand Down Expand Up @@ -57,6 +59,7 @@ class ConnectorConfiguration {
private final int responseTimeout;
private final int connectTimeout;
private final ProxyConfiguration proxyConfiguration;
private final AtomicReference<SSLParamConfigurator> sniConfigs = new AtomicReference<>(null);

ConnectorConfiguration(Client client, Configuration config) {
final Map<String, Object> properties = config.getProperties();
Expand Down Expand Up @@ -170,6 +173,14 @@ public ProxyConfiguration getProxyConfiguration() {
return proxyConfiguration;
}

void setSniConfig(SSLParamConfigurator sniConfig) {
this.sniConfigs.compareAndSet(null, sniConfig);
}

SSLParamConfigurator getSniConfig() {
return sniConfigs.get();
}

@Override
public String toString() {
return "ConnectorConfiguration{"
Expand Down
Loading

0 comments on commit a89d668

Please sign in to comment.