Mohan
Mohan

Reputation: 887

could not work ssl connection properly when keystore file changed dynamically through web application

am trying to change the truststore path dynamically using java web application.

am developing struts application and login is based on ldap through secure socket layer (ssl) connection.

To connect with ssl i have created .cer file using java keytool option.

Now i able to connect with ldap and i can retieve user information from ldap.

when i change the ssl certificate(invalid certificate for testing) dynamically it could not give any exceptions. but it works when i restart the tomcat server.

following is the code that i am trying

try{
        java.io.InputStream in = new java.io.FileInputStream("C:\\test.cer");
        java.security.cert.Certificate c = java.security.cert.CertificateFactory.getInstance("X.509").generateCertificate(in);
        java.security.KeyStore ks = java.security.KeyStore.getInstance("JKS");
        ks.load(null); 
        if (!ks.containsAlias("alias ldap")) { 
            ks.setCertificateEntry("alias ldap", c);
        }
        java.io.OutputStream out = new java.io.FileOutputStream("C:\\ldap.jks");
        char[] kspass = "changeit".toCharArray();
        ks.store(out, kspass);
        out.close();
        System.setProperty("javax.net.ssl.trustStore", "C:\\ldap.jks");
        System.setProperty("javax.net.ssl.trustStorePassword", "changeit");    
    }catch(Exception e){
        e.printStackTrace();
    }

is there any mistake that i made with the code? does any new code that i need to put to connect dynamically?

Note : instead of c:\ldap.jks file i gave invalid file dynamically. it does not give any exception.

Edited (checked with custom TrustManager) :

i also implemented TrustManager and ssl context is initialized with custom trust manager. but i am not able to get the expected behaviour

could u please help me. the code that i tried is

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.UUID;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class TestDynamicSSLCert {

    public static void main(String[] args)throws NamingException,IOException {
        DataInputStream din = new DataInputStream (System.in);
        String yes = "yes";

        String certpath = "C:\\cert.cer";
        String ldappath1 = "C:\\ldap.jks";
        String ldappath2 = "C:\\ldap.jks";    // setting valid key store path    

        while("yes".equalsIgnoreCase(yes.trim())){            
            System.out.println(" ldappath2 : "+ldappath2);
            Hashtable env = new Hashtable();
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            env.put(Context.SECURITY_PRINCIPAL,"uid=admin,ou=system");
            env.put(Context.SECURITY_CREDENTIALS, "secret");
            env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.PROVIDER_URL, "ldaps://172.16.12.4:636/ou=system");
            try {

                java.io.InputStream in = new java.io.FileInputStream(certpath);
                java.security.cert.Certificate c = java.security.cert.CertificateFactory.getInstance("X.509").generateCertificate(in);
                java.security.KeyStore ks = java.security.KeyStore.getInstance("JKS");
                ks.load(null);
                if (!ks.containsAlias("alias ldap")) {
                    ks.setCertificateEntry("alias ldap", c);
                }
                java.io.OutputStream out = new java.io.FileOutputStream(ldappath1);
                char[] kspass = "changeit".toCharArray();
                ks.store(out, kspass);
                out.close();
                System.setProperty("javax.net.ssl.trustStore", ldappath2);
                System.setProperty("javax.net.ssl.trustStorePassword", "changeit");              
                // Custorm trust manager 
                MyX509TrustManager reload = new MyX509TrustManager(ldappath2,c);
                TrustManager[] tms = new TrustManager[] { reload };
                javax.net.ssl.SSLContext sslCtx = javax.net.ssl.SSLContext.getInstance("SSL");
                sslCtx.init(null, tms, null);  
                // Custom trust manager
            } catch (Exception e) {
                e.printStackTrace();
            }
            DirContext ctx = new InitialDirContext(env);
            NamingEnumeration enm = ctx.list("");
            while (enm.hasMore()) {
                System.out.println(enm.next());
            }                    
            ctx.close();
            System.out.println(" Go again by yes/no :");
            yes = din.readLine();
            ldappath2 = "C:\\invalidldap.jks"; // setting invalid keystore path

        }
    }
}

class MyX509TrustManager implements X509TrustManager {

    private final String trustStorePath;
    private X509TrustManager trustManager;
    private List<Certificate> tempCertList = new ArrayList<Certificate>();

    public MyX509TrustManager(String tspath,Certificate cert)throws Exception{
        this.trustStorePath = tspath;        
        tempCertList.add(cert);
        reloadTrustManager();
    }

    public MyX509TrustManager(String tspath)
            throws Exception {
        this.trustStorePath = tspath;
        reloadTrustManager();
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain,
            String authType) throws CertificateException {
        trustManager.checkClientTrusted(chain, authType);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain,
            String authType) throws CertificateException {
        try {
            trustManager.checkServerTrusted(chain, authType);
        } catch (CertificateException cx) {
            addServerCertAndReload(chain[0], true);
            trustManager.checkServerTrusted(chain, authType);
        }
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        X509Certificate[] issuers = trustManager.getAcceptedIssuers();
        return issuers;
    }

    private void reloadTrustManager() throws Exception {

        // load keystore from specified cert store (or default)
        KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
        InputStream in = new FileInputStream(trustStorePath);
        try {
            ts.load(in, null);
        } finally {
            in.close();
        }

        // add all temporary certs to KeyStore (ts)
        for (Certificate cert : tempCertList) {         
            ts.setCertificateEntry(UUID.randomUUID().toString(), cert);
        }

        // initialize a new TMF with the ts we just loaded
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ts);

        // acquire X509 trust manager from factory
        TrustManager tms[] = tmf.getTrustManagers();

        for (int i = 0; i < tms.length; i++) {
            if (tms[i] instanceof X509TrustManager) {                
                trustManager = (X509TrustManager) tms[i];
                return;
            }
        }

        throw new NoSuchAlgorithmException("No X509TrustManager in TrustManagerFactory");
    }

    private void addServerCertAndReload(Certificate cert,
            boolean permanent) {
        try {
            if (permanent) {
                // import the cert into file trust store
                // Google "java keytool source" or just ...
                Runtime.getRuntime().exec("keytool -importcert ...");
            } else {
                tempCertList.add(cert);
            }
            reloadTrustManager();
        } catch (Exception ex) { /* ... */ }
    }
}

Expected Behaviour :

ldap connection should be successfull with valid keystore file (during first loop ). if user give yes then invalid keystore is assigned and need to produce exception and should not connect to ldap

Actual Behaviour:

for both valid keystore file i able to retrieve information from ldap.

Note :

if i set String ldappath2 = "C:\invalidldap.jks"; at begining, it gives exception.

Why am doing this ?

@EJP, because, i need to develope module which is based on ldap authentication securely. module should support multiple ldap servers. ldap settings can be inserted from the UI (webpage that has the ui to get the details like ldaphost, port, basedn, and ssl certificate) and this details should go to database. at the same time certificate also present in database. work for module is just retrieve the users from ldap and store it to another table. so if we change the new ldap server setting with new certificate means, System.setProperty("javax.net.ssl.trustStore","truststorepath") will fail. are you okay with my explanation?

Upvotes: 2

Views: 6345

Answers (2)

user207421
user207421

Reputation: 310909

Is there any other way to connect ldap securely with out using above steps?

Yes, but why do you think you need to know?

Does the application (tomcat or single java file) should be restarted whenever trustStore property is updated ?

Yes.

Upvotes: 0

user207421
user207421

Reputation: 310909

You are correct. You have to restart Tomcat when you change the keystore or truststore. You don't need to write code to load client certificates, you just need to make sure you are dealing with servers whose certificates are signed by a CA that you trust. Adding new certificates at runtime is radically insecure.

Upvotes: 3

Related Questions