3

I need to provide truststore and keystore when trying to connect to ldap sever using spring LdapTemplate. I can't import the certification using keytool or set by system property javax.net.ssl.trustStore

Now I config in following way:

   <bean id="authenticationStrategy" class="org.springframework.ldap.core.support.DefaultTlsDirContextAuthenticationStrategy">
       <property name="sslSocketFactory">
            <bean class="com.sc.cops.common.auth.ssl.SslSocketFactoryBuilder">
                <property name="trustStoreLocation" value="${oud.ldap.keyTrustLocation}" />
                <property name="trustStorePassword" value="Password1" />
              <!-- <property name="trustStorePassword">
                   <enc:decrypt key="${common.encryption.key}" cipher-text="${common.ssl.mq.keystore.password}" />
                </property> -->  
                <property name="keyStoreLocation" value="${oud.ldap.keyTrustLocation}" />
                <property name="keyStorePassword" value="Password1" />
            </bean>
        </property>
    </bean>

    <bean id="ldapContextSource"
        class="org.springframework.ldap.core.support.LdapContextSource">
        <property name="url" value="ldaps://${ldap.server}:${ldap.port}/" />
        <property name="userDn" value="${ldap.oud.userDn}" />
        <property name="password" value="${ldap.oud.password}" />
        <property name="authenticationStrategy" ref="authenticationStrategy"/>
    </bean>

    <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
        <property name="contextSource" ref="ldapContextSource" />
    </bean>

In com.sc.cops.common.auth.ssl.SslSocketFactoryBuilder, we create SSLSocketFactory:

@Override
public SSLSocketFactory getObject() throws IOException, GeneralSecurityException {

    TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    KeyStore trustStore = loadKeyStore(getTrustStoreLocation(), getTrustStoreType(), getTrustStorePassword());
    trustMgrFactory.init(trustStore);

    KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    KeyStore keyStore = loadKeyStore(getKeyStoreLocation(), getKeyStoreType(), getKeyStorePassword());
    keyMgrFactory.init(keyStore, toCharArray(getKeyStorePassword()));

    SSLContext sslContext = SSLContext.getInstance(SSL_VERSION);
    sslContext.init(keyMgrFactory.getKeyManagers(), trustMgrFactory.getTrustManagers(), null);

    return sslContext.getSocketFactory();
}

I debug the code, I can see the keystore and truststore are loaded correctly. And the jks file I provided is correct. But when I try to authenticate, I got error

Root exception is javax.net.ssl.SSLHandshakeException: 
sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find 
valid certification path to requested target.
Kitty Yu
  • 31
  • 1
  • 4
  • There are several ways to set system properties. Do you load a properties file ? – Eugène Adell Jan 23 '18 at 14:26
  • I say can't set by system properties because in our system we already set another jks file using this way. And I don't want to add this new certification into the existing jks file. – Kitty Yu Jan 24 '18 at 01:10
  • This question is asked almost everyday here. Either you're not loading the right keystore, or it doesn't contain the trusted certificate that you believe. Add -Djavax.net.debug=all and check the keystore loading, and its certificates. Compare the certificate with the server's one (compare the serial numbers) – Eugène Adell Jan 24 '18 at 06:23
  • 1
    @EugèneAdell this is definitely not a question asked every day, and it is not straightforward to achieve what the OP wants. –  Oct 16 '18 at 14:27
  • @Lezard I asked the debug logs, which the user didn't post. – Eugène Adell Oct 16 '18 at 19:06

2 Answers2

1

I was unable to get it to work with the AuthenticationStrategy class.

We solved it by:

  • implementing a custom socket factory
  • add the this to the BaseEnvironmentProperties in the ldap context config.
  • using an initializing bean to allow to stick to the spring like configuration.

Implement an initializing bean:

   public class SystemPropertyInitializer implements InitializingBean {
      @Value("${truststore.path}")
      private String truststorePath;

     @Value("${truststore.pass}")
     private String truststorePass;

     @Override
     public void afterPropertiesSet() throws Exception {
        System.setProperty(Utils.SOCKET_FACTORY_TSPATH, truststorePath);
        System.setProperty(Utils.SOCKET_FACTORY_TSPASS, truststorePass);
     }
  1. when setting up the ldap template:

    LdapContextSource contextSource = new LdapContextSource();
    contextSource.setUrl(getUrl());
    contextSource.setBase(getBase());
    contextSource.setUserDn(getUser());
    contextSource.setPassword(getLdapPass());
    Map<String, Object> baseEnvironmentProperties = new HashMap<String, Object>();
    baseEnvironmentProperties.put("java.naming.ldap.factory.socket", "com.custom.LdapSocketFactory");
    contextSource.setBaseEnvironmentProperties(baseEnvironmentProperties );
    contextSource.afterPropertiesSet();
    

I could not find another way to pass the socket factory... ie via Autowired... So the implementation of the socket factory loaded the trust information from the env variables.

The SSL exception occurs at the point when the initial context was initialized. From what I can see is that prior to loading the initial context the authenticationStrategy.setupEnvironment(env, principal, credentials) is called. In the class AbstractTlsDirContextAuthenticationStrategy this method does nothing. So the factory does not get set there... Anyway, I think this is more of an authentication thing on the ldap server ie once the handshake has occurred. I mean, it does say as much in the class name :-)

see also: how to accept self-signed certificates for JNDI/LDAP connections?

1

Very late response, but, for future developers facing the same issue:

Use the sslcontext-kickstart library which lets you use multiple sources for your truststores (e.g. JDK + one from the local filesystem + one from the classpath, etc.).

Source

Eduardo
  • 2,070
  • 21
  • 26