Thread: SSL connection and client verification

SSL connection and client verification

From
Vic Simkus
Date:
Hello

Attached is a file that "implements" client authentication when using
SSL (i.e. send the client cert to the server).  The difference between
this and the code from
http://archives.postgresql.org/pgsql-jdbc/2006-02/msg00166.php is that
this implementation does not require any changes to existing code.

Any comments?  Can we get this into the source tree?

Thanks

--
Vic Simkus

Department of Neurology, UIC
912 South Wood St.
Room 855N
Chicago IL 60612


package org.postgresql.ssl;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.Properties;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class ValidatingFactory extends WrappedFactory {

    /**
     * Template string for the property name which indicates the name of a
     * key/trust store file.
     */
    private static final String SSL__FILE = "ssl._.file";

    /**
     * Template string for the property name which indicates the type of
     * key/trust store to instanciate.
     */
    private static final String SSL__TYPE = "ssl._.type";

    /**
     * Template String for the property name which indicates a password to a
     * key/trust store.
     */
    private static final String SSL__PASSWORD = "ssl._.password";

    /**
     * Template String for the property name which indicates the algorithm to
     * use when instanciateing a Key/TrustManagerFactory. This is somewhat
     * redundant since the default algorithm may be set via your security
     * provider.
     */
    private static final String SSL__ALGORITHM = "ssl._.algorithm";

    /**
     * String used to differenciate a keystore from a truststore in the Property
     * templates.
     */
    private static final String KEYSTORE = "keystore";

    /**
     * Property used to indicate the keystore file. If this property does
     * not exist in the Properties object passed to the consturctor, then the
     * file System.getProperty("user.home")/postgresql/.postgresql.jks will be
     * used instead.
     */
    public static final String SSL_KEYSTORE_FILE = SSL__FILE.replaceFirst("_",
            KEYSTORE);

    /**
     * Property used to indicate the type of keystore which will be loaded. The
     * supported values depend on your security provider. KeyStore.getDefaultType()
     * is used, if this property is not present.
     *
     * @see KeyStore#getDefaultType()
     */
    public static final String SSL_KEYSTORE_TYPE = SSL__TYPE.replaceFirst("_",
            KEYSTORE);

    /**
     * Property used to indicate the password which will be used to load the key
     * store. If this option is not present, then an empty password will be
     * used. Note that some keystore tools will not allow you to create a keystore
     * without a password.
     */
    public static final String SSL_KEYSTORE_PASSWORD = SSL__PASSWORD
            .replaceFirst("_", KEYSTORE);

    /**
     * Property used to indicate the algorithm which should be used when
     * creating the KeyManagerFactory, which will in turn supply the KeyManagers
     * to the SSLContext. If this property is not present, then
     * KeyManagerFactory.getDefaultAlgorithm will be used instead.
     *
     * @see KeyManagerFactory#getDefaultAlgorithm()
     */
    public static final String SSL_KEYSTORE_ALGORITHM = SSL__ALGORITHM
            .replaceFirst("_", KEYSTORE);

    /**
     * The property name used to indicate that the default JRE key managers
     * should be used. The existance of this property in the Properties object
     * passed to the constuctor indicates that the default should be used, and
     * the value is ignored.
     * <p>
     * This option will have the affect of passing a null argument to the
     * SSLContext.init method. This behavior is useful if you are connecting to
     * a server which does not require client authentication.
     * <p>
     * All other SSL_KEYSTORE_* options will be ignored if this option is set.
     *
     * @see SSLContext#init(KeyManager[], TrustManager[],
     *      java.security.SecureRandom)
     */
    public static final String SSL_USE_DEFAULT_KEY_MANAGER = "ssl.use.default.key.manager";

    /**
     * String used to differenciate a truststore from a keystore in the Property
     * templates.
     */
    private static final String TRUSTSTORE = "truststore";

    /**
     * Property used to indicate the struststore file. If this property does
     * not exist in the Properties object passed to the consturctor, then the
     * file System.getProperty("user.home")/postgresql/.postgresql.jks will be
     * used instead.
     */
    public static final String SSL_TRUSTSTORE_FILE = SSL__FILE.replaceFirst(
            "_", TRUSTSTORE);

    /**
     * Property used to indicate the type of keystore which will be loaded. The
     * supported values depend on your security provider. KeyStore.getDefaultTYpe
     * is used, if this property is not present.
     *
     * @see KeyStore#getDefaultType()
     */
    public static final String SSL_TRUSTSTORE_TYPE = SSL__TYPE.replaceFirst(
            "_", TRUSTSTORE);

    /**
     * Property used to indicate the password which will be used to load the key
     * store. If this option is not present, then an empty password will be
     * used. A trust store commonly does not contain secure information, so
     * it is likely that this option is not required.
     */
    public static final String SSL_TRUSTSTORE_PASSWORD = SSL__PASSWORD
            .replaceFirst("_", TRUSTSTORE);

    /**
     * Property used to indicate the algorithm which should be used when
     * creating the TrustManagerFactory, which will in turn supply the TrustManagers
     * to the SSLContext. If this property is not present, then
     * TrustManagerFactory.getDefaultAlgorithm will be used instead.
     *
     * @see TrustManagerFactory#getDefaultAlgorithm()
     */
    public static final String SSL_TRUSTSTORE_ALGORITHM = SSL__ALGORITHM
            .replaceFirst("_", TRUSTSTORE);

    /**
     * The property name used to indicate that the default JRE trust managers
     * should be used. The existance of this property in the Properties object
     * passed to the constuctor indicates that the default should be used, and
     * the value is ignored.
     * <p>
     * This option will have the affect of passing a null argument to the
     * SSLContext.init method. This behavior is useful if you are connecting to
     * a server which has a certificate signed by a widely accepted Certificate
     * Authority. See the java API for details.
     * <p>
     * All other SSL_TRUSTSTORE_* options will be ignored if this option is set.
     *
     * @see SSLContext#init(KeyManager[], TrustManager[],
     *      java.security.SecureRandom)
     */
    public static final String SSL_USE_DEFAULT_TRUST_MANAGER = "ssl.use.default.trust.manager";

    /**
     * A Property used to indicate that when constructing the trust store, the
     * information provided for the key store should be used. This existance of
     * the property is used to indicate that this behavior is desired, and the
     * value is ignored.
     * <p>
     * This is useful in cases where the the root server certificate is stored
     * in the client's keystore, which is a common case.
     */
    public static final String SSL_KEYSTORE_IS_TRUSTSTORE = "ssl.keystore.is.truststore";

    /**
     * A Property name used to identify the ssl protocol to use to connect to
     * the server. The supported values depends on your security provider. If
     * no option is specified, then SSLv3 is used.
     */
    public static final String SSL_PROTOCOL = "ssl.protocol";

    /**
     * A Property used to indicate a custom security provider.
     */
    public static final String SSL_PROVIDER = "ssl.provider";

    /**
     * Constructor which uses the property values specified by the
     * public static values of this class to retrieve ssl factory
     * configuration options.
     *
     * @param props
     * @throws Exception This constructor can fail for a wide
     *                   variety of reasons, all of which result in some
     *                   sort of Exception.
     */
    public ValidatingFactory(Properties props) throws Exception {

        // Obtain the trust managers for the ssl context.
        TrustManager[] trustManagers = null;
        if (props.getProperty(SSL_USE_DEFAULT_TRUST_MANAGER) == null) {
            trustManagers = createTrustManagers(props);
        }

        // Obtain the key managers for the ssl context.
        KeyManager[] keyManagers = null;
        if (props.getProperty(SSL_USE_DEFAULT_KEY_MANAGER) == null) {
            keyManagers = createKeyManagers(props);
        }

        // Create the ssl context.
        String protocol = getProtocol(props);
        SSLContext sslContext = null;
        if (props.getProperty(SSL_PROVIDER) != null) {
            sslContext = SSLContext.getInstance(protocol, props
                    .getProperty(SSL_PROVIDER));
        } else {
            sslContext = SSLContext.getInstance(protocol);
        }

        // initialize the context.
        sslContext.init(keyManagers, trustManagers, null);

        // Create the wrapped socket factory.
        _factory = sslContext.getSocketFactory();
    }

    /**
     * @param props The properties object passed to the constructor should
     *              be passed to thie method.
     * @return Array of TrustManagers which should be used to initialize
     *                 the SSLContext.
     * @throws Exception
     */
    protected static TrustManager[] createTrustManagers(Properties props)
            throws Exception {
        // We may want to simply used the key store for trusted certificates.
        String mode = TRUSTSTORE;
        if (props.getProperty(SSL_KEYSTORE_IS_TRUSTSTORE) != null) {
            mode = KEYSTORE;
        }

        // Obtain the keystore.
        KeyStore trustStore = getLoadedKeyStore(props, mode);


        String provider = props.getProperty(SSL_PROVIDER);
        String algorithm = getAlgorithm(props, mode);
        TrustManagerFactory tmf = null;

        if (provider != null) {
            tmf = TrustManagerFactory.getInstance(algorithm, provider);
        } else {
            tmf = TrustManagerFactory.getInstance(algorithm);
        }

        tmf.init(trustStore);

        return tmf.getTrustManagers();
    }

    /**
     * @param props Argument should be the same as the one passed to
     *              the constructor.
     * @return A KeyManager array intended for use for the SSLContext.init method.
     * @throws Exception Many reasons this could fail...
     */
    private static KeyManager[] createKeyManagers(Properties props)
            throws Exception {
        KeyStore trustStore = getLoadedKeyStore(props, KEYSTORE);

        String provider = props.getProperty(SSL_PROVIDER);
        String algorithm = getAlgorithm(props, KEYSTORE);
        KeyManagerFactory kmf = null;
        if (provider != null) {
            kmf = KeyManagerFactory.getInstance(algorithm, provider);
        } else {
            kmf = KeyManagerFactory.getInstance(algorithm);
        }

        char[] password = getPWD(props, KEYSTORE);
        try {
            kmf.init(trustStore, password);
        }finally {
            Arrays.fill(password, '\0');
        }

        return kmf.getKeyManagers();
    }

    private static String getAlgorithm(Properties props, String mode) {
        String ans = props.getProperty(SSL__ALGORITHM.replaceFirst("_", mode));
        if (ans != null) {
            return ans;
        }

        if (mode.equals(KEYSTORE)) {
            return KeyManagerFactory.getDefaultAlgorithm();
        } else {
            return TrustManagerFactory.getDefaultAlgorithm();
        }
    }

    private static KeyStore getLoadedKeyStore(Properties props, String mode)
            throws Exception {
        File keystoreFile = getStoreFile(props, mode);

        String keyStoreType = getStoreType(props, mode, keystoreFile);

        String provider = props.getProperty(SSL_PROVIDER);
        KeyStore store = null;
        if (provider != null) {
            store = KeyStore.getInstance(keyStoreType, provider);
        } else {
            store = KeyStore.getInstance(keyStoreType);
        }

        InputStream strm = new FileInputStream(keystoreFile);
        char[] password = getPWD(props, mode);
        try {
            store.load(strm, password);
        } finally {
            Arrays.fill(password, '\0');
        }

        strm.close();

        return store;
    }

    private static File getStoreFile(Properties props, String mode) {
        String ks = props.getProperty(SSL__FILE.replaceFirst("_", mode));
        if (ks != null) {
            return new File(ks);
        }

        File ans = new File(System.getProperty("user.home"));
        ans = new File(ans, ".postgresql");
        ans = new File(ans, "postgresql.jks");
        return ans;
    }

    private static String getStoreType(Properties props, String mode, File keyStore) {
        String kst = props.getProperty(SSL__TYPE.replaceFirst("_", mode));
        if (kst != null) {
            return kst;
        }

        return KeyStore.getDefaultType();
    }

    private static char[] getPWD(Properties props, String mode) {
        // Unfortunately, the password is in a string. We don't really
        // have anyway of wiping it.
        String pwd = props.getProperty(SSL__PASSWORD.replaceFirst("_", mode),
                "");
        return pwd.toCharArray();
    }

    private static String getProtocol(Properties props) {
        if (props.containsKey(SSL_PROTOCOL)) {
            return props.getProperty(SSL_PROTOCOL);
        }
        return "SSLv3";
    }
}

Re: SSL connection and client verification

From
Vic Simkus
Date:
Vic Simkus wrote:
> Hello
>
> Attached is a file that "implements" client authentication when using
> SSL (i.e. send the client cert to the server).  The difference between
> this and the code from
> http://archives.postgresql.org/pgsql-jdbc/2006-02/msg00166.php is that
> this implementation does not require any changes to existing code.
> Any comments?  Can we get this into the source tree?
Sorry about that - attached the wrong file.

package org.postgresql.ssl;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

import java.security.KeyStore;

import java.util.Arrays;
import java.util.Properties;
import java.security.Security;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.postgresql.ssl.WrappedFactory;

/**
Provides an implementation of socket factory that supports client authentication (sending client certificate to the
server)
 
for the PGSQL JDBC driver.  Most of this seems to happen automagicaly when a key manager is supplied to the SSLContext
init
 
method 

We try to use the JSSE system properties for configuration.

Properties used:
<ul>
    <li>javax.net.ssl.keyStorePassword - key store password.  If not set defaults to 'changeit' </li>
    <li>javax.net.ssl.keyStoreProvider - key store provider.  Should not need to be explicitly set.</li>
    <li>javax.net.ssl.keyStoreType - type of the key store.  Should not need to be explicitly set.</li>
    <li>javax.net.ssl.keyStore - Location of the key store.  If not set defaults to 'user.home/.keystore'.  If 
        specifyig explicitly  include the <b>complete</b> file system path</li>
    
    <li>javax.net.ssl.trustStore - location of the trust store (if the trust store is different from the key store).  
        If this is not set the keyStore location will be used.  Include the <b>complete</b> file system path</li>
    <li>javax.net.ssl.trustStorePassword - trust store password.  If not set defaults to 'changeit' </li>
    <li>javax.net.ssl.trustStoreProvider - trust store provider.  Should not need to be explicitly set.</li>
    <li>javax.net.ssl.trustStoreType - trust store type.  Should not need to be explicitly set.<li>
    
    <li>ssl.KeyManagerFactory.algorithm - Algoritm for the key manager factory.  Defaults to 'SunX509'</li>
    <li>ssl.TrustManagerFactory.algorithm - Algorithm for the trust manager factory.  Defaults to 'PKIX'</li>
</ul>

An attempt it made to make everything happen with as little configuration as possible.  If a trust store is not
specified
 
the key store is used instead.  If the key store is not specified an attempt is made to use the one created by default
by keytool - {user.home}/.keystore.  If the key store password is not specified 'changeit' is used.  Basically, if a
key
store is maintained using the default setting for keytool everything should work out of the box.

References:

http://java.sun.com/j2se/1.5.0/docs/guide/security/jsse/JSSERefGuide.html
http://archives.postgresql.org/pgsql-jdbc/2006-02/msg00166.php

@author Vic Simkus (vic.simkus@simkus.com)
*/
public class ValidatingFactory  extends WrappedFactory
{
    private String _key_store_password = null;
    private String _key_store_provider = null;
    private String _key_store_type = null;
    private String _key_store_location = null;
    
    private String _trust_store_location = null;
    private String _trust_store_password = null;
    private String _trust_store_provider = null;
    private String _trust_store_type = null;
    
    private String _key_manager_factory_algo = null;
    private String _trust_manager_factory_algo = null;
    
    /**
    XXX Does this need to be customizable?
    Defaults to SSLv3
    */
    private String _ssl_protocol = null;
    
    /**
    XXX Does this need to be customizable?
    Defaults to SunJSSE
    */
    private String _ssl_ctx_provider = null;
    
    private KeyStore _key_store_instance = null;
    private KeyStore _trust_store_instance = null;
    
    private KeyManager [] _key_managers = null;
    private TrustManager [] _trust_managers = null;
    
    private static final String _SYS_PROP_PREFIX = "javax.net.ssl.";
    private static final String _KEYSTORE_FILE_NAME=".keystore";
     
     public ValidatingFactory() throws Exception
    {
        _key_store_password = System.getProperty(_SYS_PROP_PREFIX + "keyStorePassword");
    _key_store_provider = System.getProperty(_SYS_PROP_PREFIX + "keyStoreProvider");
    _key_store_type = System.getProperty(_SYS_PROP_PREFIX + "keyStoreType");
    _key_store_location = System.getProperty(_SYS_PROP_PREFIX + "keyStore");
    
    _trust_store_password = System.getProperty(_SYS_PROP_PREFIX + "trustStorePassword");
    _trust_store_provider = System.getProperty(_SYS_PROP_PREFIX + "trustStoreProvider");
    _trust_store_type = System.getProperty(_SYS_PROP_PREFIX + "trustStoreType");
    _trust_store_location = System.getProperty(_SYS_PROP_PREFIX + "trustStore");
    
    _key_manager_factory_algo = System.getProperty("ssl.KeyManagerFactory.algorithm");
    _trust_manager_factory_algo = System.getProperty("ssl.TrustManagerFactory.algorithm");
        
    if(_key_store_password == null)
    {
        _key_store_password = "changeit";
    }
    
    if(_trust_store_password == null)
    {
        _trust_store_password = "changeit";
    }
    
    if(_ssl_protocol == null) //this will always be null initial since we're not trying to configure it via the sytem
properties
    {
        _ssl_protocol = "SSLv3";
    }        
    
    if(_ssl_ctx_provider == null) //this will always be null initialy.  See above.
    {
        _ssl_ctx_provider = "SunJSSE";
    }
    
    if(_key_manager_factory_algo == null)
    {
        _key_manager_factory_algo = "SunX509";
    }
    
    if(_trust_manager_factory_algo == null)
    {
        _trust_manager_factory_algo = "PKIX";
    }
    
    if(_key_store_type == null)
    {
        _key_store_type = KeyStore.getDefaultType();
    }
    
    if(_trust_store_type == null)
    {
        _trust_store_type = _key_store_type;
    }
    
    if(_key_store_provider == null)
    {
        _key_store_provider = KeyStore.getInstance(_key_store_type).getProvider().getName();
    }
    
    if(_trust_store_provider == null)
    {
        _trust_store_provider = _key_store_provider;
    }
    
    /*
    Go on a hunt for the key store.
    */
    if(_key_store_location == null)
    {
        File keystore_file = new File(System.getProperty("user.home"),_KEYSTORE_FILE_NAME);
        
        if(keystore_file.isFile())
        {
            _key_store_location = keystore_file.getAbsolutePath();
        }
        else
        {
            throw new Error("Failed to find default key store (.keystore in user's home directory)");
        }
    }
    
    if(_trust_store_location == null)
    {
        _trust_store_password = _key_store_password;
        _trust_store_provider = _key_store_provider;
        _trust_store_type = _key_store_type;
        _trust_store_location = _key_store_location;        
    }
    
    _key_store_instance = getKeyStore(_key_store_type,_key_store_provider,new
File(_key_store_location),_key_store_password);   
 
    _trust_store_instance = getKeyStore(_trust_store_type,_trust_store_provider,new
File(_trust_store_location),_trust_store_password);
    
    /*
    Get a factory of a factory of a factory...
    */
    KeyManagerFactory key_manager_factory = KeyManagerFactory.getInstance(_key_manager_factory_algo);
    key_manager_factory.init(_key_store_instance,_key_store_password.toCharArray());    
    _key_managers = key_manager_factory.getKeyManagers();
    
    TrustManagerFactory trust_manager_factory = TrustManagerFactory.getInstance(_trust_manager_factory_algo);
    trust_manager_factory.init(_trust_store_instance);
    _trust_managers = trust_manager_factory.getTrustManagers();
    
        SSLContext sslContext = SSLContext.getInstance(_ssl_protocol,_ssl_ctx_provider);
        sslContext.init(_key_managers,_trust_managers, null);

        _factory = sslContext.getSocketFactory();
    }
    
    private static KeyStore getKeyStore(String type,String provider,File file,String password) throws Exception
    {
        KeyStore key_store = KeyStore.getInstance(type,provider);
        FileInputStream key_store_input = new FileInputStream(file); 
        key_store.load(key_store_input,password.toCharArray());
        key_store_input.close();
        
        return key_store;
    }
}