diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
index 63cdab890c..e6d658800d 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
@@ -38,7 +38,7 @@ import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.keycloak.common.util.EnvUtil;
import org.keycloak.common.util.KeystoreUtil;
-import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@@ -333,7 +333,7 @@ public class HttpClientBuilder {
}
}
- public HttpClient build(AdapterConfig adapterConfig) {
+ public HttpClient build(AdapterHttpClientConfig adapterConfig) {
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
@@ -379,13 +379,13 @@ public class HttpClientBuilder {
/**
* Configures a the proxy to use for auth-server requests if provided.
*
- * If the given {@link AdapterConfig} contains the attribute {@code proxy-url} we use the
+ * If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
* given URL as a proxy server, otherwise the proxy configuration is ignored.
*
*
* @param adapterConfig
*/
- private void configureProxyForAuthServerIfProvided(AdapterConfig adapterConfig) {
+ private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
return;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java
new file mode 100644
index 0000000000..5c94fdb964
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.adapters.cloned;
+
+/**
+ * Configuration options relevant for configuring http client that can be used by adapter.
+ *
+ * NOTE: keep in sync with core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java until unified.
+ *
+ * @author hmlnarik
+ */
+public interface AdapterHttpClientConfig {
+
+ /**
+ * Returns truststore filename.
+ */
+ public String getTruststore();
+
+ /**
+ * Returns truststore password.
+ */
+ public String getTruststorePassword();
+
+ /**
+ * Returns keystore with client keys.
+ */
+ public String getClientKeystore();
+
+ /**
+ * Returns keystore password.
+ */
+ public String getClientKeystorePassword();
+
+ /**
+ * Returns boolean flag whether any hostname verification is done on the server's
+ * certificate, {@code true} means that verification is not done.
+ * @return
+ */
+ public boolean isAllowAnyHostname();
+
+ /**
+ * Returns boolean flag whether any trust management and hostname verification is done.
+ *
+ * NOTE Disabling trust manager is a security hole, so only set this option
+ * if you cannot or do not want to verify the identity of the
+ * host you are communicating with.
+ */
+ public boolean isDisableTrustManager();
+
+ /**
+ * Returns size of connection pool.
+ */
+ public int getConnectionPoolSize();
+
+ /**
+ * Returns URL of HTTP proxy.
+ */
+ public String getProxyUrl();
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java
new file mode 100644
index 0000000000..0fa330e5f6
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.adapters.cloned;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.util.EntityUtils;
+import org.keycloak.adapters.saml.descriptor.parsers.SamlDescriptorIDPKeysExtractor;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+/**
+ * @author Hynek Mlnařík
+ */
+public class HttpAdapterUtils {
+
+ public static MultivaluedHashMap downloadKeysFromSamlDescriptor(HttpClient client, String descriptorUrl) throws HttpClientAdapterException {
+ try {
+ HttpGet httpRequest = new HttpGet(descriptorUrl);
+ HttpResponse response = client.execute(httpRequest);
+ int status = response.getStatusLine().getStatusCode();
+ if (status != HttpStatus.SC_OK) {
+ EntityUtils.consumeQuietly(response.getEntity());
+ throw new HttpClientAdapterException("Unexpected status = " + status);
+ }
+
+ HttpEntity entity = response.getEntity();
+ if (entity == null) {
+ throw new HttpClientAdapterException("There was no entity.");
+ }
+
+ MultivaluedHashMap res;
+ try (InputStream is = entity.getContent()) {
+ res = extractKeysFromSamlDescriptor(is);
+ }
+
+ EntityUtils.consumeQuietly(entity);
+
+ return res;
+ } catch (IOException | ParsingException e) {
+ throw new HttpClientAdapterException("IO error", e);
+ }
+ }
+
+ /**
+ * Parses SAML descriptor and extracts keys from it.
+ * @param xmlStream
+ * @return List of KeyInfo objects containing keys from the descriptor.
+ * @throws IOException
+ */
+ public static MultivaluedHashMap extractKeysFromSamlDescriptor(InputStream xmlStream) throws ParsingException {
+ Object res = new SamlDescriptorIDPKeysExtractor().parse(xmlStream);
+ return (MultivaluedHashMap) res;
+ }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java
new file mode 100644
index 0000000000..e0371adc05
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.adapters.cloned;
+
+/**
+ * @author Marek Posolda
+ */
+public class HttpClientAdapterException extends Exception {
+
+ public HttpClientAdapterException(String message) {
+ super(message);
+ }
+
+ public HttpClientAdapterException(String message, Throwable t) {
+ super(message, t);
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java
new file mode 100644
index 0000000000..7e26c01488
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.adapters.cloned;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
+import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.SingleClientConnManager;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.keycloak.common.util.EnvUtil;
+import org.keycloak.common.util.KeystoreUtil;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.net.URI;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Abstraction for creating HttpClients. Allows SSL configuration.
+ *
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class HttpClientBuilder {
+ public static enum HostnameVerificationPolicy {
+ /**
+ * Hostname verification is not done on the server's certificate
+ */
+ ANY,
+ /**
+ * Allows wildcards in subdomain names i.e. *.foo.com
+ */
+ WILDCARD,
+ /**
+ * CN must match hostname connecting to
+ */
+ STRICT
+ }
+
+
+ /**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+ private static class PassthroughTrustManager implements X509TrustManager {
+ public void checkClientTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ }
+
+ protected KeyStore truststore;
+ protected KeyStore clientKeyStore;
+ protected String clientPrivateKeyPassword;
+ protected boolean disableTrustManager;
+ protected boolean disableCookieCache = true;
+ protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
+ protected SSLContext sslContext;
+ protected int connectionPoolSize = 100;
+ protected int maxPooledPerRoute = 0;
+ protected long connectionTTL = -1;
+ protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
+ protected HostnameVerifier verifier = null;
+ protected long socketTimeout = -1;
+ protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
+ protected long establishConnectionTimeout = -1;
+ protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
+ protected HttpHost proxyHost;
+
+
+ /**
+ * Socket inactivity timeout
+ *
+ * @param timeout
+ * @param unit
+ * @return
+ */
+ public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) {
+ this.socketTimeout = timeout;
+ this.socketTimeoutUnits = unit;
+ return this;
+ }
+
+ /**
+ * When trying to make an initial socket connection, what is the timeout?
+ *
+ * @param timeout
+ * @param unit
+ * @return
+ */
+ public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) {
+ this.establishConnectionTimeout = timeout;
+ this.establishConnectionTimeoutUnits = unit;
+ return this;
+ }
+
+ public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
+ this.connectionTTL = ttl;
+ this.connectionTTLUnit = unit;
+ return this;
+ }
+
+ public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
+ this.maxPooledPerRoute = maxPooledPerRoute;
+ return this;
+ }
+
+ public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
+ this.connectionPoolSize = connectionPoolSize;
+ return this;
+ }
+
+ /**
+ * Disable trust management and hostname verification. NOTE this is a security
+ * hole, so only set this option if you cannot or do not want to verify the identity of the
+ * host you are communicating with.
+ */
+ public HttpClientBuilder disableTrustManager() {
+ this.disableTrustManager = true;
+ return this;
+ }
+
+ public HttpClientBuilder disableCookieCache() {
+ this.disableCookieCache = true;
+ return this;
+ }
+
+ /**
+ * SSL policy used to verify hostnames
+ *
+ * @param policy
+ * @return
+ */
+ public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
+ this.policy = policy;
+ return this;
+ }
+
+
+ public HttpClientBuilder sslContext(SSLContext sslContext) {
+ this.sslContext = sslContext;
+ return this;
+ }
+
+ public HttpClientBuilder trustStore(KeyStore truststore) {
+ this.truststore = truststore;
+ return this;
+ }
+
+ public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
+ this.clientKeyStore = keyStore;
+ this.clientPrivateKeyPassword = password;
+ return this;
+ }
+
+ public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
+ this.clientKeyStore = keyStore;
+ this.clientPrivateKeyPassword = new String(password);
+ return this;
+ }
+
+
+ static class VerifierWrapper implements X509HostnameVerifier {
+ protected HostnameVerifier verifier;
+
+ VerifierWrapper(HostnameVerifier verifier) {
+ this.verifier = verifier;
+ }
+
+ @Override
+ public void verify(String host, SSLSocket ssl) throws IOException {
+ if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
+ }
+
+ @Override
+ public void verify(String host, X509Certificate cert) throws SSLException {
+ throw new SSLException("This verification path not implemented");
+ }
+
+ @Override
+ public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
+ throw new SSLException("This verification path not implemented");
+ }
+
+ @Override
+ public boolean verify(String s, SSLSession sslSession) {
+ return verifier.verify(s, sslSession);
+ }
+ }
+
+ public HttpClient build() {
+ X509HostnameVerifier verifier = null;
+ if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
+ else {
+ switch (policy) {
+ case ANY:
+ verifier = new AllowAllHostnameVerifier();
+ break;
+ case WILDCARD:
+ verifier = new BrowserCompatHostnameVerifier();
+ break;
+ case STRICT:
+ verifier = new StrictHostnameVerifier();
+ break;
+ }
+ }
+ try {
+ SSLSocketFactory sslsf = null;
+ SSLContext theContext = sslContext;
+ if (disableTrustManager) {
+ theContext = SSLContext.getInstance("SSL");
+ theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
+ new SecureRandom());
+ verifier = new AllowAllHostnameVerifier();
+ sslsf = new SniSSLSocketFactory(theContext, verifier);
+ } else if (theContext != null) {
+ sslsf = new SniSSLSocketFactory(theContext, verifier);
+ } else if (clientKeyStore != null || truststore != null) {
+ sslsf = new SniSSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
+ } else {
+ final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
+ tlsContext.init(null, null, null);
+ sslsf = new SniSSLSocketFactory(tlsContext, verifier);
+ }
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(
+ new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
+ Scheme httpsScheme = new Scheme("https", 443, sslsf);
+ registry.register(httpsScheme);
+ ClientConnectionManager cm = null;
+ if (connectionPoolSize > 0) {
+ ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit);
+ tcm.setMaxTotal(connectionPoolSize);
+ if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
+ tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
+ cm = tcm;
+
+ } else {
+ cm = new SingleClientConnManager(registry);
+ }
+ BasicHttpParams params = new BasicHttpParams();
+
+ if (proxyHost != null) {
+ params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
+ }
+
+ if (socketTimeout > -1) {
+ HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
+
+ }
+ if (establishConnectionTimeout > -1) {
+ HttpConnectionParams.setConnectionTimeout(params, (int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
+ }
+ DefaultHttpClient client = new DefaultHttpClient(cm, params);
+
+ if (disableCookieCache) {
+ client.setCookieStore(new CookieStore() {
+ @Override
+ public void addCookie(Cookie cookie) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public List getCookies() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean clearExpired(Date date) {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void clear() {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+ });
+
+ }
+ return client;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public HttpClient build(AdapterHttpClientConfig adapterConfig) {
+ disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
+
+ String truststorePath = adapterConfig.getTruststore();
+ if (truststorePath != null) {
+ truststorePath = EnvUtil.replace(truststorePath);
+ String truststorePassword = adapterConfig.getTruststorePassword();
+ try {
+ this.truststore = KeystoreUtil.loadKeyStore(truststorePath, truststorePassword);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load truststore", e);
+ }
+ }
+ String clientKeystore = adapterConfig.getClientKeystore();
+ if (clientKeystore != null) {
+ clientKeystore = EnvUtil.replace(clientKeystore);
+ String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
+ try {
+ KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
+ keyStore(clientCertKeystore, clientKeystorePassword);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load keystore", e);
+ }
+ }
+ int size = 10;
+ if (adapterConfig.getConnectionPoolSize() > 0)
+ size = adapterConfig.getConnectionPoolSize();
+ HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
+ if (adapterConfig.isAllowAnyHostname())
+ policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
+ connectionPoolSize(size);
+ hostnameVerification(policy);
+ if (adapterConfig.isDisableTrustManager()) {
+ disableTrustManager();
+ } else {
+ trustStore(truststore);
+ }
+
+ configureProxyForAuthServerIfProvided(adapterConfig);
+
+ return build();
+ }
+
+ /**
+ * Configures a the proxy to use for auth-server requests if provided.
+ *
+ * If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
+ * given URL as a proxy server, otherwise the proxy configuration is ignored.
+ *
+ *
+ * @param adapterConfig
+ */
+ private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
+
+ if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
+ return;
+ }
+
+ URI uri = URI.create(adapterConfig.getProxyUrl());
+ this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+ }
+}
\ No newline at end of file
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java
new file mode 100644
index 0000000000..8c064b0ded
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.adapters.cloned;
+
+import org.apache.http.HttpHost;
+import org.apache.http.conn.scheme.HostNameResolver;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import org.apache.http.protocol.HttpContext;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.security.AccessController;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * SSLSocketFactory that uses Server Name Indication (SNI) TLS extension.
+ *
+ *