Merge pull request #2000 from mstruk/truststore
KEYCLOAK-1717 Truststore SPI and file provider
This commit is contained in:
commit
a6c852603e
41 changed files with 766 additions and 106 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<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>
|
||||
|
|
|
@ -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 {
|
||||
if (httpClient != null) {
|
||||
httpClient.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
|
@ -87,36 +93,49 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
|||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
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 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");
|
||||
|
||||
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)
|
||||
.hostnameVerification(hostnamePolicy)
|
||||
.disableCookies(disableCookies);
|
||||
if (disableTrustManager) builder.disableTrustManager();
|
||||
if (truststore != null) {
|
||||
truststore = EnvUtil.replace(truststore);
|
||||
|
||||
if (disableTrustManager) {
|
||||
// TODO: is it ok to do away with disabling trust manager?
|
||||
//builder.disableTrustManager();
|
||||
} else {
|
||||
builder.hostnameVerification(hostnamePolicy);
|
||||
try {
|
||||
builder.trustStore(KeystoreUtil.loadKeyStore(truststore, truststorePassword));
|
||||
builder.trustStore(truststoreProvider.getTruststore());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to load truststore", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (clientKeystore != null) {
|
||||
clientKeystore = EnvUtil.replace(clientKeystore);
|
||||
try {
|
||||
|
@ -128,6 +147,9 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
|||
}
|
||||
httpClient = builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
|
|
@ -145,7 +145,7 @@ public class HttpClientBuilder {
|
|||
* Disable cookie management.
|
||||
*/
|
||||
public HttpClientBuilder disableCookies(boolean disable) {
|
||||
this.disableTrustManager = disable;
|
||||
this.disableCookies = disable;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<module>mongo</module>
|
||||
<module>mongo-update</module>
|
||||
<module>http-client</module>
|
||||
<module>truststore</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
|
|
35
connections/truststore/pom.xml
Executable file
35
connections/truststore/pom.xml
Executable file
|
@ -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>
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.connections.truststore.FileTruststoreProviderFactory
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.connections.truststore.TruststoreSpi
|
5
dependencies/server-min/pom.xml
vendored
5
dependencies/server-min/pom.xml
vendored
|
@ -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>
|
||||
|
|
|
@ -45,9 +45,7 @@
|
|||
},
|
||||
|
||||
"connectionsHttpClient": {
|
||||
"default": {
|
||||
"disable-trust-manager": true
|
||||
}
|
||||
"default": {}
|
||||
},
|
||||
|
||||
"connectionsJpa": {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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:
|
||||
|
@ -421,15 +419,6 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
|
|||
</para>
|
||||
</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>
|
||||
|
@ -439,44 +428,6 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
|
|||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>hostname-verification-policy</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.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>truststore</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.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>truststore-password</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Password for the truststore keystore.
|
||||
This is
|
||||
<emphasis>REQUIRED</emphasis>
|
||||
if
|
||||
<literal>truststore</literal>
|
||||
is set.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>client-keystore</term>
|
||||
<listitem>
|
||||
|
@ -516,13 +467,111 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
|
|||
</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>file</term>
|
||||
<listitem>
|
||||
<para>
|
||||
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>password</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Password for the truststore.
|
||||
This is
|
||||
<emphasis>REQUIRED</emphasis>
|
||||
if <literal>disabled</literal> is not true.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>hostname-verification-policy</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.
|
||||
</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>
|
||||
|
|
|
@ -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();
|
||||
|
|
5
pom.xml
5
pom.xml
|
@ -640,6 +640,11 @@
|
|||
<artifactId>keycloak-connections-mongo-update</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</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>
|
||||
|
|
|
@ -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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ public class DefaultEmailSenderProviderFactory implements EmailSenderProviderFac
|
|||
|
||||
@Override
|
||||
public EmailSenderProvider create(KeycloakSession session) {
|
||||
return new DefaultEmailSenderProvider();
|
||||
return new DefaultEmailSenderProvider(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,9 +51,7 @@
|
|||
},
|
||||
|
||||
"connectionsHttpClient": {
|
||||
"default": {
|
||||
"disable-trust-manager": true
|
||||
}
|
||||
"default": {}
|
||||
},
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue