keycloak-uncached
Changes
connections/http-client/pom.xml 4(+4 -0)
connections/http-client/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java 100(+61 -39)
connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java 2(+1 -1)
connections/pom.xml 1(+1 -0)
connections/truststore/pom.xml 35(+35 -0)
connections/truststore/src/main/java/org/keycloak/connections/truststore/FileTruststoreProvider.java 31(+31 -0)
connections/truststore/src/main/java/org/keycloak/connections/truststore/FileTruststoreProviderFactory.java 102(+102 -0)
connections/truststore/src/main/java/org/keycloak/connections/truststore/HostnameVerificationPolicy.java 19(+19 -0)
connections/truststore/src/main/java/org/keycloak/connections/truststore/JSSETruststoreConfigurator.java 106(+106 -0)
connections/truststore/src/main/java/org/keycloak/connections/truststore/SSLSocketFactory.java 87(+87 -0)
connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProvider.java 15(+15 -0)
connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProviderFactory.java 9(+9 -0)
connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreProviderSingleton.java 17(+17 -0)
connections/truststore/src/main/java/org/keycloak/connections/truststore/TruststoreSpi.java 31(+31 -0)
connections/truststore/src/main/resources/META-INF/services/org.keycloak.connections.truststore.TruststoreProviderFactory 1(+1 -0)
dependencies/server-min/pom.xml 5(+4 -1)
distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json 4(+1 -3)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-core/main/module.xml 1(+1 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-oidc/main/module.xml 1(+1 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-http-client/main/module.xml 1(+1 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-truststore/main/module.xml 17(+17 -0)
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 1(+1 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml 1(+1 -0)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-core/main/module.xml 1(+1 -0)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-broker-oidc/main/module.xml 1(+1 -0)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-http-client/main/module.xml 1(+1 -0)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-truststore/main/module.xml 17(+17 -0)
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 1(+1 -0)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml 1(+1 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java 3(+3 -0)
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());
}
}
}
connections/http-client/pom.xml 4(+4 -0)
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;
}
connections/pom.xml 1(+1 -0)
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>
connections/truststore/pom.xml 35(+35 -0)
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
dependencies/server-min/pom.xml 5(+4 -1)
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