-
Notifications
You must be signed in to change notification settings - Fork 485
Open
Milestone
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
deepd, JanHron and sparklton
Metadata
Metadata
Assignees
Labels
No labels