keycloak-uncached

Merge pull request #2000 from mstruk/truststore KEYCLOAK-1717

1/11/2016 6:24:53 AM

Changes

pom.xml 5(+5 -0)

Details

diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java b/broker/core/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java
index 5fa23b0..b10c46e 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java
@@ -1,5 +1,8 @@
 package org.keycloak.broker.provider.util;
 
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -24,6 +27,9 @@ public class SimpleHttp {
     private Map<String, String> headers;
     private Map<String, String> params;
 
+    private SSLSocketFactory sslFactory;
+    private HostnameVerifier hostnameVerifier;
+
     protected SimpleHttp(String url, String method) {
         this.url = url;
         this.method = method;
@@ -53,6 +59,15 @@ public class SimpleHttp {
         return this;
     }
 
+    public SimpleHttp sslFactory(SSLSocketFactory factory) {
+        sslFactory = factory;
+        return this;
+    }
+
+    public SimpleHttp hostnameVerifier(HostnameVerifier verifier) {
+        hostnameVerifier = verifier;
+        return this;
+    }
 
     public String asString() throws IOException {
         boolean get = method.equals("GET");
@@ -85,6 +100,7 @@ public class SimpleHttp {
         }
 
         HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+        setupTruststoreIfApplicable(connection);
         OutputStream os = null;
         InputStream is = null;
 
@@ -171,6 +187,7 @@ public class SimpleHttp {
         }
 
         HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+        setupTruststoreIfApplicable(connection);
         OutputStream os = null;
         InputStream is = null;
 
@@ -235,4 +252,13 @@ public class SimpleHttp {
         return writer.toString();
     }
 
+    private void setupTruststoreIfApplicable(HttpURLConnection connection) {
+        if (connection instanceof HttpsURLConnection && sslFactory != null) {
+            HttpsURLConnection con = (HttpsURLConnection) connection;
+            con.setSSLSocketFactory(sslFactory);
+            if (hostnameVerifier != null) {
+                con.setHostnameVerifier(hostnameVerifier);
+            }
+        }
+    }
 }
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index 0c8dc3c..6e2543b 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -27,6 +27,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
 import org.keycloak.broker.provider.AuthenticationRequest;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.connections.truststore.JSSETruststoreConfigurator;
 import org.keycloak.events.Errors;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
@@ -247,12 +248,15 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
         }
 
         public SimpleHttp generateTokenRequest(String authorizationCode) {
+            JSSETruststoreConfigurator configurator = new JSSETruststoreConfigurator(session);
             return SimpleHttp.doPost(getConfig().getTokenUrl())
                     .param(OAUTH2_PARAMETER_CODE, authorizationCode)
                     .param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
                     .param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret())
                     .param(OAUTH2_PARAMETER_REDIRECT_URI, uriInfo.getAbsolutePath().toString())
-                    .param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE);
+                    .param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE)
+                    .sslFactory(configurator.getSSLSocketFactory())
+                    .hostnameVerifier(configurator.getHostnameVerifier());
         }
     }
 }
diff --git a/connections/http-client/pom.xml b/connections/http-client/pom.xml
index f88ee65..07e7f6f 100755
--- a/connections/http-client/pom.xml
+++ b/connections/http-client/pom.xml
@@ -23,6 +23,10 @@
             <artifactId>keycloak-model-api</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-connections-truststore</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
             <scope>provided</scope>
diff --git a/connections/http-client/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
index a06b296..67f7cbd 100755
--- a/connections/http-client/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
+++ b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
@@ -10,6 +10,7 @@ import org.apache.http.entity.ContentType;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.jboss.logging.Logger;
 import org.keycloak.Config;
+import org.keycloak.connections.truststore.TruststoreProvider;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.common.util.EnvUtil;
@@ -28,9 +29,12 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
     private static final Logger logger = Logger.getLogger(DefaultHttpClientFactory.class);
 
     private volatile CloseableHttpClient httpClient;
+    private Config.Scope config;
 
     @Override
     public HttpClientProvider create(KeycloakSession session) {
+        lazyInit(session);
+
         return new HttpClientProvider() {
             @Override
             public HttpClient getHttpClient() {
@@ -74,7 +78,9 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
     @Override
     public void close() {
         try {
-            httpClient.close();
+            if (httpClient != null) {
+                httpClient.close();
+            }
         } catch (IOException e) {
 
         }
@@ -87,46 +93,62 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
 
     @Override
     public void init(Config.Scope config) {
-        long socketTimeout = config.getLong("socket-timeout-millis", -1L);
-        long establishConnectionTimeout = config.getLong("establish-connection-timeout-millis", -1L);
-        int maxPooledPerRoute = config.getInt("max-pooled-per-route", 0);
-        int connectionPoolSize = config.getInt("connection-pool-size", 200);
-        boolean disableTrustManager = config.getBoolean("disable-trust-manager", false);
-        boolean disableCookies = config.getBoolean("disable-cookies", true);
-        String hostnameVerificationPolicy = config.get("hostname-verification-policy", "WILDCARD");
-        HttpClientBuilder.HostnameVerificationPolicy hostnamePolicy = HttpClientBuilder.HostnameVerificationPolicy.valueOf(hostnameVerificationPolicy);
-        String truststore = config.get("truststore");
-        String truststorePassword = config.get("truststore-password");
-        String clientKeystore = config.get("client-keystore");
-        String clientKeystorePassword = config.get("client-keystore-password");
-        String clientPrivateKeyPassword = config.get("client-key-password");
-
-        HttpClientBuilder builder = new HttpClientBuilder();
-        builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
-               .establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS)
-                .maxPooledPerRoute(maxPooledPerRoute)
-                .connectionPoolSize(connectionPoolSize)
-                .hostnameVerification(hostnamePolicy)
-                .disableCookies(disableCookies);
-        if (disableTrustManager) builder.disableTrustManager();
-        if (truststore != null) {
-            truststore = EnvUtil.replace(truststore);
-            try {
-                builder.trustStore(KeystoreUtil.loadKeyStore(truststore, truststorePassword));
-            } catch (Exception e) {
-                throw new RuntimeException("Failed to load truststore", e);
-            }
-        }
-        if (clientKeystore != null) {
-            clientKeystore = EnvUtil.replace(clientKeystore);
-            try {
-                KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
-                builder.keyStore(clientCertKeystore, clientPrivateKeyPassword);
-            } catch (Exception e) {
-                throw new RuntimeException("Failed to load keystore", e);
+        this.config = config;
+    }
+
+    private void lazyInit(KeycloakSession session) {
+        if (httpClient == null) {
+            synchronized(this) {
+                if (httpClient == null) {
+                    long socketTimeout = config.getLong("socket-timeout-millis", -1L);
+                    long establishConnectionTimeout = config.getLong("establish-connection-timeout-millis", -1L);
+                    int maxPooledPerRoute = config.getInt("max-pooled-per-route", 0);
+                    int connectionPoolSize = config.getInt("connection-pool-size", 200);
+                    boolean disableCookies = config.getBoolean("disable-cookies", true);
+                    String clientKeystore = config.get("client-keystore");
+                    String clientKeystorePassword = config.get("client-keystore-password");
+                    String clientPrivateKeyPassword = config.get("client-key-password");
+
+                    TruststoreProvider truststoreProvider = session.getProvider(TruststoreProvider.class);
+                    boolean disableTrustManager = truststoreProvider == null || truststoreProvider.getTruststore() == null;
+                    if (disableTrustManager) {
+                        logger.warn("Truststore is disabled");
+                    }
+                    HttpClientBuilder.HostnameVerificationPolicy hostnamePolicy = disableTrustManager ? null
+                            : HttpClientBuilder.HostnameVerificationPolicy.valueOf(truststoreProvider.getPolicy().name());
+
+                    HttpClientBuilder builder = new HttpClientBuilder();
+                    builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
+                            .establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS)
+                            .maxPooledPerRoute(maxPooledPerRoute)
+                            .connectionPoolSize(connectionPoolSize)
+                            .disableCookies(disableCookies);
+
+                    if (disableTrustManager) {
+                        // TODO: is it ok to do away with disabling trust manager?
+                        //builder.disableTrustManager();
+                    } else {
+                        builder.hostnameVerification(hostnamePolicy);
+                        try {
+                            builder.trustStore(truststoreProvider.getTruststore());
+                        } catch (Exception e) {
+                            throw new RuntimeException("Failed to load truststore", e);
+                        }
+                    }
+
+                    if (clientKeystore != null) {
+                        clientKeystore = EnvUtil.replace(clientKeystore);
+                        try {
+                            KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
+                            builder.keyStore(clientCertKeystore, clientPrivateKeyPassword);
+                        } catch (Exception e) {
+                            throw new RuntimeException("Failed to load keystore", e);
+                        }
+                    }
+                    httpClient = builder.build();
+                }
             }
         }
-        httpClient = builder.build();
     }
 
     @Override
diff --git a/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
index 0668382..a16c12a 100755
--- a/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
+++ b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
@@ -145,7 +145,7 @@ public class HttpClientBuilder {
      * Disable cookie management.
      */
     public HttpClientBuilder disableCookies(boolean disable) {
-        this.disableTrustManager = disable;
+        this.disableCookies = disable;
         return this;
     }
 
diff --git a/connections/pom.xml b/connections/pom.xml
index b931994..17cca7f 100755
--- a/connections/pom.xml
+++ b/connections/pom.xml
@@ -19,6 +19,7 @@
         <module>mongo</module>
         <module>mongo-update</module>
         <module>http-client</module>
+        <module>truststore</module>
     </modules>
 
     <build>
diff --git a/connections/truststore/pom.xml b/connections/truststore/pom.xml
new file mode 100755
index 0000000..d72b6d8
--- /dev/null
+++ b/connections/truststore/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.8.0.CR1-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-connections-truststore</artifactId>
+    <name>Keycloak Truststore</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/connections/truststore/src/main/java/org/keycloak/connections/truststore/FileTruststoreProvider.java b/connections/truststore/src/main/java/org/keycloak/connections/truststore/FileTruststoreProvider.java
new file mode 100644
index 0000000..d49ddb1
--- /dev/null
+++ b/connections/truststore/src/main/java/org/keycloak/connections/truststore/FileTruststoreProvider.java
@@ -0,0 +1,31 @@
+package org.keycloak.connections.truststore;
+
+import java.security.KeyStore;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class FileTruststoreProvider implements TruststoreProvider {
+
+    private final HostnameVerificationPolicy policy;
+    private final KeyStore truststore;
+
+    FileTruststoreProvider(KeyStore truststore, HostnameVerificationPolicy policy) {
+        this.policy = policy;
+        this.truststore = truststore;
+    }
+
+    @Override
+    public HostnameVerificationPolicy getPolicy() {
+        return policy;
+    }
+
+    @Override
+    public KeyStore getTruststore() {
+        return truststore;
+    }
+
+    @Override
+    public void close() {
+    }
+}
diff --git a/connections/truststore/src/main/java/org/keycloak/connections/truststore/FileTruststoreProviderFactory.java b/connections/truststore/src/main/java/org/keycloak/connections/truststore/FileTruststoreProviderFactory.java
new file mode 100644
index 0000000..0e2a5d3
--- /dev/null
+++ b/connections/truststore/src/main/java/org/keycloak/connections/truststore/FileTruststoreProviderFactory.java
@@ -0,0 +1,102 @@
+package org.keycloak.connections.truststore;
+
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class FileTruststoreProviderFactory implements TruststoreProviderFactory {
+
+    private static final Logger log = Logger.getLogger(FileTruststoreProviderFactory.class);
+
+    private TruststoreProvider provider;
+
+    @Override
+    public TruststoreProvider create(KeycloakSession session) {
+        return provider;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+        String storepath = config.get("file");
+        String pass = config.get("password");
+        String policy = config.get("hostname-verification-policy");
+        Boolean disabled = config.getBoolean("disabled", null);
+
+        // if "truststore" . "file" is not configured then it is disabled
+        if (storepath == null && pass == null && policy == null && disabled == null) {
+            return;
+        }
+
+        // if explicitly disabled
+        if (disabled != null && disabled) {
+            return;
+        }
+
+        HostnameVerificationPolicy verificationPolicy = null;
+        KeyStore truststore = null;
+
+        if (storepath == null) {
+            throw new RuntimeException("Attribute 'file' missing in 'truststore':'file' configuration");
+        }
+        if (pass == null) {
+            throw new RuntimeException("Attribute 'password' missing in 'truststore':'file' configuration");
+        }
+
+        try {
+            truststore = loadStore(storepath, pass == null ? null :pass.toCharArray());
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to initialize TruststoreProviderFactory: " + new File(storepath).getAbsolutePath(), e);
+        }
+        if (policy == null) {
+            verificationPolicy = HostnameVerificationPolicy.WILDCARD;
+        } else {
+            try {
+                verificationPolicy = HostnameVerificationPolicy.valueOf(policy);
+            } catch (Exception e) {
+                throw new RuntimeException("Invalid value for 'hostname-verification-policy': " + policy + " (must be one of: ANY, WILDCARD, STRICT)");
+            }
+        }
+
+        provider = new FileTruststoreProvider(truststore, verificationPolicy);
+        TruststoreProviderSingleton.set(provider);
+        log.debug("File trustore provider initialized: " + new File(storepath).getAbsolutePath());
+    }
+
+    private KeyStore loadStore(String path, char[] password) throws Exception {
+        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+        InputStream is = new FileInputStream(path);
+        try {
+            ks.load(is, password);
+            return ks;
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "file";
+    }
+}
diff --git a/connections/truststore/src/main/java/org/keycloak/connections/truststore/HostnameVerificationPolicy.java b/connections/truststore/src/main/java/org/keycloak/connections/truststore/HostnameVerificationPolicy.java
new file mode 100644
index 0000000..0b6c53b
--- /dev/null
+++ b/connections/truststore/src/main/java/org/keycloak/connections/truststore/HostnameVerificationPolicy.java
@@ -0,0 +1,19 @@
+package org.keycloak.connections.truststore;
+
+public 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
+}
\ No newline at end of file
diff --git a/connections/truststore/src/main/java/org/keycloak/connections/truststore/JSSETruststoreConfigurator.java b/connections/truststore/src/main/java/org/keycloak/connections/truststore/JSSETruststoreConfigurator.java
new file mode 100644
index 0000000..e323f7a
--- /dev/null
+++ b/connections/truststore/src/main/java/org/keycloak/connections/truststore/JSSETruststoreConfigurator.java
@@ -0,0 +1,106 @@
+package org.keycloak.connections.truststore;
+
+import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class JSSETruststoreConfigurator {
+
+    private TruststoreProvider provider;
+    private volatile javax.net.ssl.SSLSocketFactory sslFactory;
+    private volatile TrustManager[] tm;
+
+    public JSSETruststoreConfigurator(KeycloakSession session) {
+        KeycloakSessionFactory factory = session.getKeycloakSessionFactory();
+        TruststoreProviderFactory truststoreFactory = (TruststoreProviderFactory) factory.getProviderFactory(TruststoreProvider.class, "file");
+
+        provider = truststoreFactory.create(session);
+        if (provider != null && provider.getTruststore() == null) {
+            provider = null;
+        }
+    }
+
+    public JSSETruststoreConfigurator(TruststoreProvider provider) {
+        this.provider = provider;
+    }
+
+    public javax.net.ssl.SSLSocketFactory getSSLSocketFactory() {
+        if (provider == null) {
+            return null;
+        }
+
+        if (sslFactory == null) {
+            synchronized(this) {
+                if (sslFactory == null) {
+                    try {
+                        SSLContext sslctx = SSLContext.getInstance("TLS");
+                        sslctx.init(null, getTrustManagers(), null);
+                        sslFactory = sslctx.getSocketFactory();
+                    } catch (Exception e) {
+                        throw new RuntimeException("Failed to initialize SSLContext: ", e);
+                    }
+                }
+            }
+        }
+        return sslFactory;
+    }
+
+    public TrustManager[] getTrustManagers() {
+        if (provider == null) {
+            return null;
+        }
+
+        if (tm == null) {
+            synchronized (this) {
+                if (tm == null) {
+                    TrustManagerFactory tmf = null;
+                    try {
+                        tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+                        tmf.init(provider.getTruststore());
+                        tm = tmf.getTrustManagers();
+                    } catch (Exception e) {
+                        throw new RuntimeException("Failed to initialize TrustManager: ", e);
+                    }
+                }
+            }
+        }
+        return tm;
+    }
+
+    public HostnameVerifier getHostnameVerifier() {
+        if (provider == null) {
+            return null;
+        }
+
+        HostnameVerificationPolicy policy = provider.getPolicy();
+        switch (policy) {
+            case ANY:
+                return new HostnameVerifier() {
+                    @Override
+                    public boolean verify(String s, SSLSession sslSession) {
+                        return true;
+                    }
+                };
+            case WILDCARD:
+                return new BrowserCompatHostnameVerifier();
+            case STRICT:
+                return new StrictHostnameVerifier();
+            default:
+                throw new IllegalStateException("Unknown policy: " + policy.name());
+        }
+    }
+
+    public TruststoreProvider getProvider() {
+        return provider;
+    }
+}
diff --git a/connections/truststore/src/main/java/org/keycloak/connections/truststore/SSLSocketFactory.java b/connections/truststore/src/main/java/org/keycloak/connections/truststore/SSLSocketFactory.java
new file mode 100644
index 0000000..86f5667
--- /dev/null
+++ b/connections/truststore/src/main/java/org/keycloak/connections/truststore/SSLSocketFactory.java
@@ -0,0 +1,87 @@
+package org.keycloak.connections.truststore;
+
+import org.jboss.logging.Logger;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+
+/**
+ * Using this class is ugly, but it is the only way to push our truststore to the default LDAP client implementation.
+ * <p>
+ * This SSLSocketFactory can only use truststore configured by TruststoreProvider after the ProviderFactory was
+ * initialized using standard Spi load / init mechanism. That will only happen if "truststore" provider is configured
+ * in keycloak-server.json.
+ * <p>
+ * If TruststoreProvider is not available this SSLSocketFactory will delegate all operations to javax.net.ssl.SSLSocketFactory.getDefault().
+ *
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+
+public class SSLSocketFactory extends javax.net.ssl.SSLSocketFactory {
+
+    private static final Logger log = Logger.getLogger(SSLSocketFactory.class);
+
+    private static SSLSocketFactory instance;
+
+    private final javax.net.ssl.SSLSocketFactory sslsf;
+
+    private SSLSocketFactory() {
+
+        TruststoreProvider provider = TruststoreProviderSingleton.get();
+        javax.net.ssl.SSLSocketFactory sf = null;
+        if (provider != null) {
+            sf = new JSSETruststoreConfigurator(provider).getSSLSocketFactory();
+        }
+
+        if (sf == null) {
+            log.info("No truststore provider found - using default SSLSocketFactory");
+            sf = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault();
+        }
+
+        sslsf = sf;
+    }
+
+    public static synchronized SSLSocketFactory getDefault() {
+        if (instance == null) {
+            instance = new SSLSocketFactory();
+        }
+        return instance;
+    }
+
+    @Override
+    public String[] getDefaultCipherSuites() {
+        return sslsf.getDefaultCipherSuites();
+    }
+
+    @Override
+    public String[] getSupportedCipherSuites() {
+        return sslsf.getSupportedCipherSuites();
+    }
+
+    @Override
+    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
+        return sslsf.createSocket(socket, host, port, autoClose);
+    }
+
+    @Override
+    public Socket createSocket(String host, int port) throws IOException {
+        return sslsf.createSocket(host, port);
+    }
+
+    @Override
+    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
+        return sslsf.createSocket(host, port, localHost, localPort);
+    }
+
+    @Override
+    public Socket createSocket(InetAddress host, int port) throws IOException {
+        return sslsf.createSocket(host, port);
+    }
+
+    @Override
+    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+        return sslsf.createSocket(address, port, localAddress, localPort);
+    }
+}
diff --git a/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProvider.java b/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProvider.java
new file mode 100644
index 0000000..54cc6c3
--- /dev/null
+++ b/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProvider.java
@@ -0,0 +1,15 @@
+package org.keycloak.connections.truststore;
+
+import org.keycloak.provider.Provider;
+
+import java.security.KeyStore;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public interface TruststoreProvider extends Provider {
+
+    HostnameVerificationPolicy getPolicy();
+
+    KeyStore getTruststore();
+}
diff --git a/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProviderFactory.java b/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProviderFactory.java
new file mode 100644
index 0000000..10ed867
--- /dev/null
+++ b/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProviderFactory.java
@@ -0,0 +1,9 @@
+package org.keycloak.connections.truststore;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public interface TruststoreProviderFactory extends ProviderFactory<TruststoreProvider> {
+}
diff --git a/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProviderSingleton.java b/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProviderSingleton.java
new file mode 100644
index 0000000..520e781
--- /dev/null
+++ b/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProviderSingleton.java
@@ -0,0 +1,17 @@
+package org.keycloak.connections.truststore;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+class TruststoreProviderSingleton {
+
+    static private TruststoreProvider provider;
+
+    static void set(TruststoreProvider tp) {
+        provider = tp;
+    }
+
+    static TruststoreProvider get() {
+        return provider;
+    }
+}
diff --git a/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreSpi.java b/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreSpi.java
new file mode 100644
index 0000000..385346e
--- /dev/null
+++ b/connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreSpi.java
@@ -0,0 +1,31 @@
+package org.keycloak.connections.truststore;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class TruststoreSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "truststore";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return TruststoreProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return TruststoreProviderFactory.class;
+    }
+}
diff --git a/connections/truststore/src/main/resources/META-INF/services/org.keycloak.connections.truststore.TruststoreProviderFactory b/connections/truststore/src/main/resources/META-INF/services/org.keycloak.connections.truststore.TruststoreProviderFactory
new file mode 100644
index 0000000..5b38b43
--- /dev/null
+++ b/connections/truststore/src/main/resources/META-INF/services/org.keycloak.connections.truststore.TruststoreProviderFactory
@@ -0,0 +1 @@
+org.keycloak.connections.truststore.FileTruststoreProviderFactory
\ No newline at end of file
diff --git a/connections/truststore/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/connections/truststore/src/main/resources/META-INF/services/org.keycloak.provider.Spi
new file mode 100644
index 0000000..3be9970
--- /dev/null
+++ b/connections/truststore/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -0,0 +1 @@
+org.keycloak.connections.truststore.TruststoreSpi
\ No newline at end of file
diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml
index 019e73c..422cc28 100755
--- a/dependencies/server-min/pom.xml
+++ b/dependencies/server-min/pom.xml
@@ -131,7 +131,10 @@
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-export-import-single-file</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-connections-truststore</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-connections-http-client</artifactId>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
index 3e4315c..a5b4d1b 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
@@ -45,9 +45,7 @@
     },
 
     "connectionsHttpClient": {
-        "default": {
-            "disable-trust-manager": true
-        }
+        "default": {}
     },
 
     "connectionsJpa": {
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-core/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-core/main/module.xml
index b7ed82e..e7e18c2 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-core/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-core/main/module.xml
@@ -13,6 +13,7 @@
         <module name="org.keycloak.keycloak-events-api"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.jboss.logging"/>
+        <module name="javax.api"/>
     </dependencies>
 
 </module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-oidc/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-oidc/main/module.xml
index 2f4a97d..d070fa9 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-oidc/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-oidc/main/module.xml
@@ -13,6 +13,7 @@
         <module name="org.keycloak.keycloak-events-api"/>
         <module name="org.keycloak.keycloak-broker-core"/>
         <module name="org.keycloak.keycloak-services"/>
+        <module name="org.keycloak.keycloak-connections-truststore"/>
         <module name="org.codehaus.jackson.jackson-core-asl"/>
         <module name="org.codehaus.jackson.jackson-mapper-asl"/>
         <module name="org.codehaus.jackson.jackson-xc"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-http-client/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-http-client/main/module.xml
index 27295dd..5611eca 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-http-client/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-http-client/main/module.xml
@@ -16,6 +16,7 @@
         <module name="org.jboss.logging"/>
         <module name="javax.api"/>
         <module name="org.apache.httpcomponents"/>
+        <module name="org.keycloak.keycloak-connections-truststore"/>
     </dependencies>
 
 </module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-truststore/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-truststore/main/module.xml
new file mode 100755
index 0000000..8599425
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-truststore/main/module.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-connections-truststore">
+    <resources>
+        <artifact name="${org.keycloak:keycloak-connections-truststore}"/>
+    </resources>
+    <dependencies>
+        <module name="org.keycloak.keycloak-core"/>
+        <module name="org.keycloak.keycloak-model-api"/>
+        <module name="org.jboss.logging"/>
+        <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
+    </dependencies>
+
+</module>
\ No newline at end of file
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index 7e61fb4..0a67753 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -8,6 +8,7 @@
             <module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
             <module name="org.keycloak.keycloak-connections-mongo" services="import"/>
             <module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
+            <module name="org.keycloak.keycloak-connections-truststore" services="import"/>
             <module name="org.keycloak.keycloak-common" services="import"/>
             <module name="org.keycloak.keycloak-core" services="import"/>
             <module name="org.keycloak.keycloak-email-api" services="import"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
index 927b03f..12d9ebb 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
@@ -18,6 +18,7 @@
         <module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
         <module name="org.keycloak.keycloak-connections-mongo" services="import"/>
         <module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
+        <module name="org.keycloak.keycloak-connections-truststore" services="import"/>
         <module name="org.keycloak.keycloak-common" services="import"/>
         <module name="org.keycloak.keycloak-core" services="import"/>
         <module name="org.keycloak.keycloak-email-api" services="import"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
index 09be9a4..97cbb6f 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
@@ -181,6 +181,10 @@
             <maven-resource group="org.keycloak" artifact="keycloak-connections-infinispan"/>
         </module-def>
 
+        <module-def name="org.keycloak.keycloak-connections-truststore">
+            <maven-resource group="org.keycloak" artifact="keycloak-connections-truststore"/>
+        </module-def>
+
         <module-def name="org.keycloak.keycloak-model-jpa">
             <maven-resource group="org.keycloak" artifact="keycloak-model-jpa"/>
         </module-def>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-core/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-core/main/module.xml
index fac17da..6cb957f 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-core/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-core/main/module.xml
@@ -13,6 +13,7 @@
         <module name="org.keycloak.keycloak-events-api"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.jboss.logging"/>
+        <module name="javax.api"/>
     </dependencies>
 
 </module>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-oidc/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-oidc/main/module.xml
index f2155cb..b872468 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-oidc/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-oidc/main/module.xml
@@ -13,6 +13,7 @@
         <module name="org.keycloak.keycloak-events-api"/>
         <module name="org.keycloak.keycloak-broker-core"/>
         <module name="org.keycloak.keycloak-services"/>
+        <module name="org.keycloak.keycloak-connections-truststore"/>
         <module name="org.codehaus.jackson.jackson-core-asl"/>
         <module name="org.codehaus.jackson.jackson-mapper-asl"/>
         <module name="org.codehaus.jackson.jackson-xc"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-http-client/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-http-client/main/module.xml
index 60489b2..a10b80f 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-http-client/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-http-client/main/module.xml
@@ -13,6 +13,7 @@
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-model-api"/>
+        <module name="org.keycloak.keycloak-connections-truststore"/>
         <module name="org.jboss.logging"/>
         <module name="javax.api"/>
         <module name="org.apache.httpcomponents"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-truststore/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-truststore/main/module.xml
new file mode 100755
index 0000000..5c44976
--- /dev/null
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-truststore/main/module.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-connections-truststore">
+    <resources>
+        <!-- Insert resources here -->
+    </resources>
+    <dependencies>
+        <module name="org.keycloak.keycloak-core"/>
+        <module name="org.keycloak.keycloak-model-api"/>
+        <module name="org.jboss.logging"/>
+        <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
+    </dependencies>
+
+</module>
\ No newline at end of file
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-eap6-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-eap6-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index 7e61fb4..0a67753 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-eap6-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-eap6-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -8,6 +8,7 @@
             <module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
             <module name="org.keycloak.keycloak-connections-mongo" services="import"/>
             <module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
+            <module name="org.keycloak.keycloak-connections-truststore" services="import"/>
             <module name="org.keycloak.keycloak-common" services="import"/>
             <module name="org.keycloak.keycloak-core" services="import"/>
             <module name="org.keycloak.keycloak-email-api" services="import"/>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
index 56f2537..893c5e2 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
@@ -18,6 +18,7 @@
         <module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
         <module name="org.keycloak.keycloak-connections-mongo" services="import"/>
         <module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
+        <module name="org.keycloak.keycloak-connections-truststore" services="import"/>
         <module name="org.keycloak.keycloak-common" services="import"/>
         <module name="org.keycloak.keycloak-core" services="import"/>
         <module name="org.keycloak.keycloak-email-api" services="import"/>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index dc7a70c..369d5f1 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -382,9 +382,7 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
                 By default the setting is like this:
 <programlisting><![CDATA[
 "connectionsHttpClient": {
-    "default": {
-        "disable-trust-manager": true
-    }
+    "default": {}
 },
 ]]></programlisting>
                 Possible configuration options are:
@@ -422,15 +420,6 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
                         </listitem>
                     </varlistentry>
                     <varlistentry>
-                        <term>disable-trust-manager</term>
-                        <listitem>
-                            <para>
-                                If true, HTTPS server certificates are not verified.  If you set this to false, you must
-                                configure a truststore.
-                            </para>
-                        </listitem>
-                    </varlistentry>
-                    <varlistentry>
                         <term>disable-cookies</term>
                         <listitem>
                             <para>
@@ -440,89 +429,149 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
                         </listitem>
                     </varlistentry>
                     <varlistentry>
-                        <term>hostname-verification-policy</term>
+                        <term>client-keystore</term>
                         <listitem>
                             <para>
-                                <literal>WILDCARD</literal> by default.  For HTTPS requests, this verifies the hostname
-                                of the server's certificate.  <literal>ANY</literal> means that the hostname is not verified.
-                                <literal>WILDCARD</literal> Allows wildcards in subdomain names i.e. *.foo.com.
-                                <literal>STRICT</literal> CN must match hostname exactly.
+                                This is the file path to a Java keystore file.
+                                This keystore contains client certificate for two-way SSL.
                             </para>
                         </listitem>
                     </varlistentry>
                     <varlistentry>
-                        <term>truststore</term>
+                        <term>client-keystore-password</term>
                         <listitem>
                             <para>
-                                The value is the file path to a Java keystore file.  If
-                                you prefix the path with <literal>classpath:</literal>, then the truststore will be obtained
-                                from the deployment's classpath instead.
-                                HTTPS
-                                requests need a way to verify the host of the server they are talking to. This is
-                                what the trustore does. The keystore contains one or more trusted
-                                host certificates or certificate authorities.
+                                Password for the client keystore.
+                                This is
+                                <emphasis>REQUIRED</emphasis>
+                                if
+                                <literal>client-keystore</literal>
+                                is set.
                             </para>
                         </listitem>
                     </varlistentry>
                     <varlistentry>
-                        <term>truststore-password</term>
+                        <term>client-key-password</term>
                         <listitem>
                             <para>
-                                Password for the truststore keystore.
+                                <emphasis>Not supported yet, but we will support in future versions.</emphasis>
+                                Password for the client's key.
                                 This is
                                 <emphasis>REQUIRED</emphasis>
                                 if
-                                <literal>truststore</literal>
+                                <literal>client-keystore</literal>
                                 is set.
                             </para>
                         </listitem>
                     </varlistentry>
+                </variablelist>
+            </para>
+        </section>
+        <section id="truststore">
+            <title>Securing Outgoing Server HTTP Requests</title>
+            <para>
+            When Keycloak connects out to remote HTTP endpoints over secure https connection, it has to validate the other
+            server's certificate in order to ensure it is connecting to a trusted server. That is necessary in order to
+            prevent man-in-the-middle attacks.
+            </para>
+            <para>
+            How certificates are validated is configured in the <literal>standalone/configuration/keycloak-server.json</literal>.
+            By default truststore provider is not configured, and any https connections fall back to standard java truststore
+            configuration as described in <ulink url="https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html">
+            Java's JSSE Reference Guide</ulink> - using <literal>javax.net.ssl.trustStore system property</literal>,
+            otherwise <literal>cacerts</literal> file that comes with java is used.
+            </para>
+            <para>
+                Truststore is used when connecting securely to identity brokers, LDAP identity providers, when sending emails,
+                and for backchannel communication with client applications.
+
+                Some of these facilities may - in case when no trusted certificate is found in your configured truststore -
+                fallback to using the JSSE provided truststore.
+
+                The default JavaMail API implementation used to send out emails behaves in this way, for example.
+            </para>
+            <para>
+            You can add your truststore configuration by using the following template:
+
+            <programlisting><![CDATA[
+"truststore": {
+    "file": {
+        "file": "path to your .jks file containing public certificates",
+        "password": "password",
+        "hostname-verification-policy": "WILDCARD",
+        "disabled": false
+    }
+}
+]]></programlisting>
+
+            </para>
+            <para>
+            Possible configuration options are:
+
+                <variablelist>
                     <varlistentry>
-                        <term>client-keystore</term>
+                        <term>file</term>
                         <listitem>
                             <para>
-                                This is the file path to a Java keystore file.
-                                This keystore contains client certificate for two-way SSL.
+                                The value is the file path to a Java keystore file.
+                                HTTPS requests need a way to verify the host of the server they are talking to. This is
+                                what the trustore does. The keystore contains one or more trusted host certificates or
+                                certificate authorities. Truststore file should only contain public certificates of your secured hosts.
+
+                                This is
+                                <emphasis>REQUIRED</emphasis>
+                                if <literal>disabled</literal> is not true.
                             </para>
                         </listitem>
                     </varlistentry>
                     <varlistentry>
-                        <term>client-keystore-password</term>
+                        <term>password</term>
                         <listitem>
                             <para>
-                                Password for the client keystore.
+                                Password for the truststore.
                                 This is
                                 <emphasis>REQUIRED</emphasis>
-                                if
-                                <literal>client-keystore</literal>
-                                is set.
+                                if <literal>disabled</literal> is not true.
                             </para>
                         </listitem>
                     </varlistentry>
                     <varlistentry>
-                        <term>client-key-password</term>
+                        <term>hostname-verification-policy</term>
                         <listitem>
                             <para>
-                                <emphasis>Not supported yet, but we will support in future versions.</emphasis>
-                                Password for the client's key.
-                                This is
-                                <emphasis>REQUIRED</emphasis>
-                                if
-                                <literal>client-keystore</literal>
-                                is set.
+                                <literal>WILDCARD</literal> by default.  For HTTPS requests, this verifies the hostname
+                                of the server's certificate.  <literal>ANY</literal> means that the hostname is not verified.
+                                <literal>WILDCARD</literal> Allows wildcards in subdomain names i.e. *.foo.com.
+                                <literal>STRICT</literal> CN must match hostname exactly.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                    <varlistentry>
+                        <term>disabled</term>
+                        <listitem>
+                            <para>
+                                If true (default value), truststore configuration will be ignored, and certificate checking will
+                                fall back to JSSE configuration as described. If set to false, you must
+                                configure <literal>file</literal>, and <literal>password</literal> for the truststore.
                             </para>
                         </listitem>
                     </varlistentry>
                 </variablelist>
             </para>
+            <para>
+            You can use <emphasis>keytool</emphasis> to create a new truststore file and add trusted host certificates to it:
+
+                <programlisting>
+$ keytool -import -alias HOSTDOMAIN -keystore truststore.jks -file host-certificate.cer
+                </programlisting>
+            </para>
         </section>
         <section id="ssl_modes">
             <title>SSL/HTTPS Requirement/Modes</title>
             <warning>
                 <para>
-                    Keycloak is not set up by default to handle SSL/HTTPS in either the
-                    war distribution or appliance.  It is highly recommended that you either enable SSL on the Keycloak server
-                    itself or on a reverse proxy in front of the Keycloak server.
+                    Keycloak is not set up by default to handle SSL/HTTPS. It is highly recommended that you either enable SSL
+                    on the Keycloak server itself or on a reverse proxy in front of the Keycloak server.
                 </para>
             </warning>
             <para>
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
index 3d0d22b..612fd73 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -467,6 +467,9 @@ public class LDAPOperationManager {
 
         if (protocol != null) {
             env.put(Context.SECURITY_PROTOCOL, protocol);
+            if ("ssl".equals(protocol)) {
+                env.put("java.naming.ldap.factory.socket", "org.keycloak.connections.truststore.SSLSocketFactory");
+            }
         }
 
         String bindDN = this.config.getBindDN();

pom.xml 5(+5 -0)

diff --git a/pom.xml b/pom.xml
index 03e1416..83ac183 100755
--- a/pom.xml
+++ b/pom.xml
@@ -642,6 +642,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-connections-truststore</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-common</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java
index 1a06877..5f7f68b 100644
--- a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java
+++ b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java
@@ -1,6 +1,9 @@
 package org.keycloak.email;
 
 import org.jboss.logging.Logger;
+import org.keycloak.connections.truststore.HostnameVerificationPolicy;
+import org.keycloak.connections.truststore.JSSETruststoreConfigurator;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 
@@ -12,6 +15,8 @@ import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
 import java.util.Date;
 import java.util.Map;
 import java.util.Properties;
@@ -23,6 +28,12 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
 
     private static final Logger log = Logger.getLogger(DefaultEmailSenderProvider.class);
 
+    private final KeycloakSession session;
+
+    public DefaultEmailSenderProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
     @Override
     public void send(RealmModel realm, UserModel user, String subject, String textBody, String htmlBody) throws EmailException {
         try {
@@ -52,6 +63,10 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
                 props.setProperty("mail.smtp.starttls.enable", "true");
             }
 
+            if (ssl || starttls) {
+                setupTruststore(props);
+            }
+
             props.setProperty("mail.smtp.timeout", "10000");
             props.setProperty("mail.smtp.connectiontimeout", "10000");
 
@@ -94,9 +109,18 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
         }
     }
 
+    private void setupTruststore(Properties props) throws NoSuchAlgorithmException, KeyManagementException {
+
+        JSSETruststoreConfigurator configurator = new JSSETruststoreConfigurator(session);
+
+        props.put("mail.smtp.ssl.socketFactory", configurator.getSSLSocketFactory());
+        if (configurator.getProvider().getPolicy() == HostnameVerificationPolicy.ANY) {
+            props.setProperty("mail.smtp.ssl.trust", "*");
+        }
+    }
+
     @Override
     public void close() {
 
     }
-
 }
diff --git a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProviderFactory.java b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProviderFactory.java
index 9677000..de8dfd7 100644
--- a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProviderFactory.java
+++ b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProviderFactory.java
@@ -11,7 +11,7 @@ public class DefaultEmailSenderProviderFactory implements EmailSenderProviderFac
 
     @Override
     public EmailSenderProvider create(KeycloakSession session) {
-        return new DefaultEmailSenderProvider();
+        return new DefaultEmailSenderProvider(session);
     }
 
     @Override
diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
index 26fec93..41c2cf5 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -51,9 +51,7 @@
     },
 
     "connectionsHttpClient": {
-        "default": {
-            "disable-trust-manager": true
-        }
+        "default": {}
     },
 
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
index 4d4c2f6..0978231 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
@@ -72,9 +72,7 @@
     },
 
     "connectionsHttpClient": {
-        "default": {
-            "disable-trust-manager": true
-        }
+        "default": {}
     },
 
 
@@ -99,5 +97,14 @@
             "databaseSchema": "${keycloak.connectionsMongo.databaseSchema:update}",
             "connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}"
         }
+    },
+
+    "truststore": {
+        "file": {
+            "file": "${keycloak.truststore.file:src/main/keystore/keycloak.truststore}",
+            "password": "${keycloak.truststore.password:secret}",
+            "hostname-verification-policy": "${keycloak.truststore.policy:WILDCARD}",
+            "disabled": "${keycloak.truststore.disabled:true}"
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java
index 4abe94d..cdc919e 100644
--- a/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java
+++ b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/LDAPEmbeddedServer.java
@@ -43,6 +43,9 @@ public class LDAPEmbeddedServer {
     private static final String DEFAULT_BIND_HOST = "localhost";
     private static final String DEFAULT_BIND_PORT = "10389";
     private static final String DEFAULT_LDIF_FILE = "classpath:ldap/default-users.ldif";
+    private static final String PROPERTY_ENABLE_SSL = "enableSSL";
+    private static final String PROPERTY_KEYSTORE_FILE = "keystoreFile";
+    private static final String PROPERTY_CERTIFICATE_PASSWORD = "certificatePassword";
 
     public static final String DSF_INMEMORY = "mem";
     public static final String DSF_FILE = "file";
@@ -56,6 +59,9 @@ public class LDAPEmbeddedServer {
     protected String ldifFile;
     protected String ldapSaslPrincipal;
     protected String directoryServiceFactory;
+    protected boolean enableSSL = false;
+    protected String keystoreFile;
+    protected String certPassword;
 
     protected DirectoryService directoryService;
     protected LdapServer ldapServer;
@@ -97,6 +103,9 @@ public class LDAPEmbeddedServer {
         this.ldifFile = readProperty(PROPERTY_LDIF_FILE, DEFAULT_LDIF_FILE);
         this.ldapSaslPrincipal = readProperty(PROPERTY_SASL_PRINCIPAL, null);
         this.directoryServiceFactory = readProperty(PROPERTY_DSF, DEFAULT_DSF);
+        this.enableSSL = Boolean.valueOf(readProperty(PROPERTY_ENABLE_SSL, "false"));
+        this.keystoreFile = readProperty(PROPERTY_KEYSTORE_FILE, null);
+        this.certPassword = readProperty(PROPERTY_CERTIFICATE_PASSWORD, null);
     }
 
     protected String readProperty(String propertyName, String defaultValue) {
@@ -194,6 +203,11 @@ public class LDAPEmbeddedServer {
 
         // Read the transports
         Transport ldap = new TcpTransport(this.bindHost, this.bindPort, 3, 50);
+        if (enableSSL) {
+            ldap.setEnableSSL(true);
+            ldapServer.setKeystoreFile(keystoreFile);
+            ldapServer.setCertificatePassword(certPassword);
+        }
         ldapServer.addTransports( ldap );
 
         // Associate the DS to this LdapServer