LDSTechForumProjects

Server Certificates Installer Utility

Portion of Application Development with Single Sign-On presented at 2010 Developer Conference

If you need to converse over SSL/TLS with a server via java code and the certificates of that server are not signed by a certificate authority whose certificates are found by default in the Sun JDK's trust store, certs or cacerts, then your connection with that server will fail with a certificate related exception. The certificate chain for that server is handed to the client as part of the SSL handshake. The tool below attempts to set up an SSL channel to that designated server and port and upon receiving the certificates loads them into the local trust store. Then another attempt is made validating that the SSL handshake then succeeds. After injecting these certificated into your local trust store you will be able to set up SSL socket connections to that server without incurring certificate issues until the certificates change or expire.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.apache.log4j.Logger;

/**
 * Utility class for loading certificates from remote servers into the local JDK's 
 * certificate store. This is written specifically with Sun's JDK certs and cacerts
 * files and their locations in mind. Other VMs may not be update-able with this 
 * utility. I execute it within my IDE or on the command line with arguments:
 * 
 * -kpwd=changeit
 * -host=<target-host>
 * -port=<target-port>
 * 
 * And it updates my local keystore with the certificates from that host and 
 * port.
 * 
 * @author boydmr
 *
 */
public class JdkCacertsValidatorAndEnabler {
    private static final Logger cLog = Logger.getLogger(JdkCacertsValidatorAndEnabler.class);

    private static final String KEYSTORE_PWD_PARAM = "-kpwd=";

	private static final String HOST_PARAM = "-host=";

	private static final String PORT_PARAM = "-port=";
	
    /**
     * If SSL is used for socket connections this method verifies that the
     * backing server is a trusted source and if not installs its
     * certificates locally so that it will be a trusted source
     * thereafter.
     *
     * @param host
     * @param port
     * @param acceptCerts
     * @param keystorePassword
     * @throws Exception
     */
    public static SSLContext getSSLContext(Context ctx)
        throws Exception {

        File cacerts = getKeystoreFile();
        KeyStore keystore = getKeystore(cacerts, ctx.keystorePwd);
        CertificateChainExpose customTrustMgr = getTrustManager(keystore);
        SocketFactory factory = buildSSLContext(customTrustMgr).getSocketFactory();

        if (ctx.acceptCerts) {
            if (! areCertsTrusted(ctx.targetHost, ctx.targetPort,
                    false, ctx.acceptCerts, factory)) {
                installServerCerts(keystore, ctx.keystorePwd,
                        ctx.targetHost, cacerts,
                        customTrustMgr.getServerCerts());

                // verify that they are now trusted.
                keystore  = getKeystore(cacerts, ctx.keystorePwd);
                customTrustMgr = getTrustManager(keystore);
                factory = buildSSLContext(customTrustMgr).getSocketFactory();

                areCertsTrusted(ctx.targetHost, ctx.targetPort,
                        true, ctx.acceptCerts, factory);
            }
        }
        else {
            areCertsTrusted(ctx.targetHost, ctx.targetPort, true,
                    ctx.acceptCerts, factory);
        }
        // if we get here they certs are trusted and we don't need the
        // layer allowing us access to the certs.
        return buildSSLContext(customTrustMgr.getWrappedTrustManager());
    }

    public static SSLContext buildSSLContext(TrustManager trustMgr)
    throws Exception {
        SSLContext sslCtx = SSLContext.getInstance("TLS");
        sslCtx.init(null, new TrustManager[] {trustMgr},
                new SecureRandom());
        return sslCtx;
    }

    /**
     * Tests if the server's certificates are trusted optionally throwing an
     * exception if they are not.
     *
     * @param host
     * @param port
     * @param throwExceptionIfNotTrusted
     * @param acceptCerts
     * @param trustMgr
     * @return
     * @throws Exception
     */
    private static boolean areCertsTrusted(String host, int port,
            boolean throwExceptionIfNotTrusted, boolean acceptCerts,
            SocketFactory factory)
    throws Exception {
        SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
        socket.setSoTimeout(5000);
        boolean trusted = false;

        try {
            cLog.info("Attempting SSL handshake with " + host + ":" + port);
            socket.startHandshake();
            socket.close();
            trusted = true;
            cLog.info("Certificates are trusted.");
        } catch (SSLException ssle) {
            cLog.error("Certificates are not trusted for this server.", ssle);
            if (! acceptCerts && throwExceptionIfNotTrusted) {
                throw new RuntimeException("The specified server " + host + ":"
                        + port + " is not a trusted server. Please install its"
                        + " certificates into the local keystore.");
            }
        }
        return trusted;
    }

    /**
     * Gets a trust manager from which we can extract the server's passed
     * certificates.
     *
     * @param keystore
     * @return
     * @throws Exception
     */
    private static CertificateChainExpose getTrustManager(KeyStore keystore)
    throws Exception {
        String algorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory trustMgrFac = TrustManagerFactory.getInstance(algorithm);

        trustMgrFac.init(keystore);
        X509TrustManager defaultTrustMgr = (X509TrustManager)trustMgrFac.getTrustManagers()[0];
        return new CertificateChainExpose(defaultTrustMgr);
    }

    /**
     * Loads the KeyStore represented by the passed-in file.
     */
    private static KeyStore getKeystore(File cacerts, String keystorePassword)
        throws Exception {
        InputStream in = new FileInputStream(cacerts);
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        try {
            keystore.load(in, keystorePassword.toCharArray());
        }
        catch (Exception e) {
            cLog.error("Unable to load keystore: " + cacerts.getAbsolutePath(),
                    e);
            throw new RuntimeException("Unable to load keystore file: "
                    + cacerts.getAbsolutePath() + ". If you have a"
                    + " custom password for the keystore re-run the service"
                    + " specifying -" + KEYSTORE_PWD_PARAM 
                    + "=your-password as a parameter.", e);
        }
        in.close();
        return keystore;
    }

    /**
     * Creates a File for the java environment's certificates file.
     * @return
     */
    private static File getKeystoreFile() {
        String keyStorePath = new StringBuffer()
        .append(System.getProperty("java.home"))
        .append(File.separatorChar)
        .append("lib")
        .append(File.separatorChar)
        .append("security")
        .append(File.separatorChar)
        .append("cacerts")
        .toString();

        File cacerts = new File(keyStorePath);
        cLog.info("Using keystore file: " + cacerts.getAbsolutePath());
        return cacerts;
    }

    /**
     * Installs the server's passed certificates into the local keystore file.
     *
     * @param cacerts
     * @param serverCerts
     */
    private static void installServerCerts(KeyStore store,
            String keystorePassword, String host, File cacerts,
            X509Certificate[] serverCerts)
    throws Exception {
        for (int c = 0; c<serverCerts.length; c++) {
            X509Certificate cert = serverCerts[c];
            String alias = host + "-" + c;
            cLog.info("\n" + cert + "\n\nAdding certificate to keystore "
                    + cacerts.getAbsolutePath() + " with alias '"
                + alias + "'");
            store.setCertificateEntry(alias, cert);
        }

        cLog.info("Saving keystore: "
                + cacerts.getAbsolutePath() + ".");
        OutputStream out = new FileOutputStream(cacerts);
        store.store(out, keystorePassword.toCharArray());
        out.close();
    }


    /**
     * Class allowing us to capture the certificate chain passed from the backing
     * ldap server to us for our trust manager to compare with certifcates in its
     * local keystore to see if we trust these certificates or their signers.
     *
     * @author Mark Boyd
     * @copyright: Copyright, 2008, The Church of Jesus Christ of Latter Day Saints
     *
     */
    public static class CertificateChainExpose implements X509TrustManager {

        private X509TrustManager wrappedTrustManager = null;
        private X509Certificate[] certs = null;

        /**
         * Create an instance with the wrapped trust manager to whom the trust
         * decision for the chain will be passed.
         *
         * @param x5tm
         */
        public CertificateChainExpose(X509TrustManager x5tm) {
            this.wrappedTrustManager = x5tm;
        }

        public X509Certificate[] getServerCerts() {
            return certs;
        }

        public TrustManager getWrappedTrustManager() {
            return wrappedTrustManager;
        }

        /**
         * Receives the certificate chain, records it, and delegates to the wrapped
         * trust manager.
         */
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            this.certs = chain;
            wrappedTrustManager.checkServerTrusted(chain, authType);
        }

        /**
         * {@link UnsupportedOperationException}
         */
        public X509Certificate[] getAcceptedIssuers() {
            throw new UnsupportedOperationException();
        }

        /**
         * {@link UnsupportedOperationException}
         */
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            throw new UnsupportedOperationException();
        }
    }
    
    /**
     * Used to pass parameters into the utility like the target host, port, and the
     * local keystore password.
     * 
     * @author boydmr
     *
     */
    public static class Context {
		public int targetPort;
		public String targetHost;
		public boolean acceptCerts = true;
		public String keystorePwd = "changeit";
    	
    }
    
    public static void main(String[] args) throws Exception {
    	Context ctx = new Context();
    	
    	for (int i=0; i<args.length; i++) {
    		if (args[i].startsWith(KEYSTORE_PWD_PARAM)) {
    			ctx.keystorePwd = args[i].substring(KEYSTORE_PWD_PARAM.length());
    		}
    		if (args[i].startsWith(HOST_PARAM)) {
    			ctx.targetHost = args[i].substring(HOST_PARAM.length());
    		}
    		if (args[i].startsWith(PORT_PARAM)) {
    			ctx.targetPort = Integer.parseInt(args[i].substring(PORT_PARAM.length()));
    		}
    		
    	}
        JdkCacertsValidatorAndEnabler.getSSLContext(ctx);
    }
}
This page was last modified on 1 April 2010, at 19:15.

Note: Content found in this wiki may not always reflect official Church information. See Terms of Use.