spring-projects / spring-projects/spring-ldap
Document how to use a custom truststore
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 375
- Forks
- 501
- Avg merge
- 6h 4m
- Merged PRs (30d)
- 63
Description
If you want to use a custom truststore, eg. with just the root CA certificate of the server, you have to do:
- implement a custom
SSLSocketFactory - implement a custom
SimpleDirContextAuthenticationStrategythat sets thejava.naming.ldap.factory.socketproperty in the#setupEnvironment(Hashtable, String, String)method. - implement a custom
DefaultSpringSecurityContextSourcethat in sets thejava.naming.ldap.factory.socketproperty in the#getAuthenticatedEnv(String, String)method
It would be good if this was documented somewhere.
Socket factory base class
package com.acme.spring.ldap;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link SSLSocketFactory} that allows to specify a custom truststore.
*/
public abstract class TruststoreSSLSocketFactory extends SSLSocketFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(TruststoreSSLSocketFactory.class);
private static final String[] CIPHER_SUITES = new String[] {
// ...
};
private final SSLSocketFactory delegate;
public TruststoreSSLSocketFactory() {
this.delegate = loadWithTrustStore(this.getTrustStoreLocation(), getTruststorePassword());
}
private static SSLSocketFactory loadWithTrustStore(String truststorePath, char[] truststorePassword) {
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("TLSv1.2");
} catch (NoSuchAlgorithmException e) {
LOGGER.warn("TLS 1.2 not available", e);
throw new RuntimeException("TLS 1.2 not available", e);
}
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance("PKCS12");
} catch (KeyStoreException e) {
LOGGER.warn("PKCS12 not supported", e);
throw new RuntimeException("PKCS12 not supported", e);
}
try (FileInputStream fileInputStream = new FileInputStream(truststorePath)) {
keyStore.load(fileInputStream, truststorePassword);
} catch (GeneralSecurityException | IOException e) {
LOGGER.warn("Could not load from: " + truststorePath, e);
throw new RuntimeException("Could not load from: " + truststorePath, e);
}
String defaultTrustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory;
try {
trustManagerFactory = TrustManagerFactory.getInstance(defaultTrustManagerAlgorithm);
} catch (NoSuchAlgorithmException e) {
LOGGER.warn("Default algorithm not supported: " + defaultTrustManagerAlgorithm, e);
throw new RuntimeException("Default algorithm not supported: " + defaultTrustManagerAlgorithm, e);
}
try {
trustManagerFactory.init(keyStore);
} catch (KeyStoreException e) {
LOGGER.warn("Could not initialize trust manager factory", e);
throw new RuntimeException("Could not initialize trust manager factory", e);
}
try {
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
} catch (KeyManagementException e) {
LOGGER.warn("Could not initialize ssl context", e);
throw new RuntimeException("Could not initialize ssl context", e);
}
return sslContext.getSocketFactory();
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return delegate.createSocket(address, port, localAddress, localPort);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return delegate.createSocket(host, port);
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return delegate.createSocket(s, host, port, autoClose);
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return delegate.createSocket(host, port, localHost, localPort);
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return delegate.createSocket(host, port);
}
@Override
public String[] getDefaultCipherSuites() {
return CIPHER_SUITES;
}
@Override
public String[] getSupportedCipherSuites() {
return CIPHER_SUITES;
}
@Override
public Socket createSocket() throws IOException {
return delegate.createSocket();
}
@Override
public Socket createSocket(Socket s, InputStream consumed, boolean autoClose) throws IOException {
return delegate.createSocket(s, consumed, autoClose);
}
protected abstract String getTrustStoreLocation();
protected abstract char[] getTruststorePassword();
}
concrete socket factory
package com.acme.spring.ldap;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
/**
* {@link SSLSocketFactory} that uses a custom truststore for acme.domain.
*/
public class AcmeDomainSSLSocketFactory extends TruststoreSSLSocketFactory {
static final String PASSPHRASE = "...";
/**
* Returns the default SSL socket factory.
*
* @return the default SocketFactory
*/
public static SocketFactory getDefault() {
return new AcmeDomainSSLSocketFactory();
}
@Override
protected char[] getTruststorePassword() {
return PASSPHRASE.toCharArray();
}
@Override
protected String getTrustStoreLocation() {
return "/opt/acme/truststore.p12";
}
}
custom authentication strategy
package com.acme.spring.ldap;
import java.util.Hashtable;
import javax.net.ssl.SSLSocketFactory;
import org.springframework.ldap.core.support.DirContextAuthenticationStrategy;
import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy;
/**
* A custom {@link DirContextAuthenticationStrategy} that allows setting a custom {@link SSLSocketFactory}.
*/
final class SslSockeFactorySimpleDirContextAuthenticationStrategy extends SimpleDirContextAuthenticationStrategy {
private final Class<? extends SSLSocketFactory> sslSocketFactoryClass;
SslSockeFactorySimpleDirContextAuthenticationStrategy(Class<? extends SSLSocketFactory> sslSocketFactoryClass) {
this.sslSocketFactoryClass = sslSocketFactoryClass;
}
Class<? extends SSLSocketFactory> getSslSocketFactoryClass() {
return sslSocketFactoryClass;
}
@Override
public void setupEnvironment(Hashtable<String, Object> env, String userDn, String password) {
super.setupEnvironment(env, userDn, password);
// https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html
env.put("java.naming.ldap.factory.socket", this.sslSocketFactoryClass.getName());
}
}
package com.acme.spring.ldapr;
import java.util.Hashtable;
import javax.net.ssl.SSLSocketFactory;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.support.DirContextAuthenticationStrategy;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
/**
* A {@link ContextSource} that allows setting a custom {@link SSLSocketFactory}.
*/
final class SslSocketFactoryContextSource extends DefaultSpringSecurityContextSource {
private DirContextAuthenticationStrategy authenticationStrategy;
SslSocketFactoryContextSource(String providerUrl) {
super(providerUrl);
}
@Override
public void setAuthenticationStrategy(DirContextAuthenticationStrategy authenticationStrategy) {
this.authenticationStrategy = authenticationStrategy;
super.setAuthenticationStrategy(authenticationStrategy);
}
@Override
protected Hashtable<String, Object> getAuthenticatedEnv(String principal, String credentials) {
Hashtable<String, Object> env = super.getAuthenticatedEnv(principal, credentials);
if (this.authenticationStrategy instanceof SslSockeFactorySimpleDirContextAuthenticationStrategy) {
// https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html
Class<? extends SSLSocketFactory> sslSocketFactoryClass = ((SslSockeFactorySimpleDirContextAuthenticationStrategy) this.authenticationStrategy).getSslSocketFactoryClass();
env.put("java.naming.ldap.factory.socket", sslSocketFactoryClass.getName());
}
return env;
}
}
usage
SslSockeFactorySimpleDirContextAuthenticationStrategy authenticationStrategy = new SslSockeFactorySimpleDirContextAuthenticationStrategy(AcmeDomainSSLSocketFactory.class);
DefaultSpringSecurityContextSource contextSource = new SslSocketFactoryContextSource(SERVER_URL);
This is a follow up to #494
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the Spring LDAP documentation around SimpleDirContextAuthenticationStrategy, setupEnvironment, DefaultSpringSecurityContextSource, and getAuthenticatedEnv. Document the custom SSLSocketFactory and truststore configuration shown in the issue, including how the socket factory is connected to LDAP authentication; the documentation is done when users can follow the example to configure a custom truststore.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- authentication, documentation, security
- Issue type
- Documentation
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100