/*-------------------------------------------------------------------------
*
* Copyright (c) 2011, PostgreSQL Global Development Group
*
*
*-------------------------------------------------------------------------
*/
package org.postgresql.ssl;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.SecureRandom;
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;
/**
* Provide an implementation of SSLSocketFactory that allows client authentication.
*
* This code creates and wraps a SSLSocketFactory using the paths and passwords
* obtained from calls to abstract methods that must be defined by a subclass.
* You cannot use this class as-is. See SysPropCertAuthFactory for one you can
* use unmodified.
*
* Subclasses must call buildSSLSocketFactory() at the end of their
* constructors, and must call the superclass constructors at the beginning
* of their constructors. Subclasses must also implement the abstract methods
* so that appropriate configuration details are returned.
*
* @author Marc-André Laverdière (marc-andre@atc.tcs.com / marcandre.laverdiere@tcs.com)
*/
////
//// TODO: Delegate trust checks to system trustmanager if
//// no match in our own trustmanager?
////
//// TODO: way to override keymanager/trustmanager supply without
//// haivng to override those abstracts?
public abstract class AbstractCertAuthFactory extends WrappedFactory {
protected final static String DEFAULT_SSL_PROTOCOL_NAME = "SSL";
protected AbstractCertAuthFactory() {
}
protected AbstractCertAuthFactory(String ignored) {
}
/**
* Builds an SSLContext with a trust store and key store constructed
* using the parameters provided by the subclass's implementation of
* AbstractCertAuthFactory's abstract methods.
*
* If a null value is passed for any argument, the system default will
* be used. For example, a null keyManagers array will cause
* the SSLSocketFactory to use an SSLContext with a system-default KeyStore.
* On the Sun JRE, this is created using the JSSE javax.net.ssl system
* properties. Similar rules apply for the TrustStore and SecureRandom instance.
*
* See http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html .
*
* @param keyManagers KeyManager(s) to obtain key material from, or null for JSSE default keystore
* @param trustManagers TrustManager(s) to obtain trust material from, or null for JSSE default truststore
* @param secureRandom Secure random generator. Unless you know you need to do otherwise, leave this null to use the default.
*
*/
protected void buildSSLSocketFactory(KeyManager[] keyManagers, TrustManager[] trustManagers, SecureRandom secureRandom) throws IOException, GeneralSecurityException{
if (_factory != null) {
throw new IllegalStateException("buildSSLSocketFactory() already called!");
}
//Create + Initialize TLS context
SSLContext context = SSLContext.getInstance(getSSLProtocolName());
context.init(keyManagers, trustManagers, secureRandom);
_factory = context.getSocketFactory();
}
/**
* Create and return KeyManager(s) using the passed information.
*
* If keyStoreType is null, the default keystore type for the system default
* JSSE provider will be used. If the keyPass is null, the keyStorePass will
* also be used to decode the key. No other arguments may be null.
*
* @param keyStorePath Location of keystore on file system. Non-null.
* @param keyStorePass Password/passphrase to decrypt keystore with. Non-null.
* @param keyPass Password/passphrase to decrypt key(s) of interest within KeyStore with. If null, keyStorePass used.
* @param keyStoreType JSSE keystore type. If null, JSSE provider default is used.
* @return KeyStore(s) created
* @throws IOException if the KeyStore could not be read
* @throws GeneralSecurityException (subtypes of) for most crypto errors, passphrase errors, etc.
*/
protected KeyManager[] createKeyManagers(String keyStorePath, char[] keyStorePass, char[] keyPass, String keyStoreType) throws IOException, GeneralSecurityException {
//Load the Key Managers
if (keyStoreType == null) {
keyStoreType = KeyStore.getDefaultType();
}
if (keyPass == null) {
keyPass = keyStorePass;
}
KeyStore ks = KeyStore.getInstance(keyStoreType);
FileInputStream fInKeyStore = new FileInputStream(keyStorePath);
try {
ks.load(fInKeyStore, keyStorePass);
} finally {
try {
fInKeyStore.close();
} catch (IOException ex) {
// Ignored
}
}
KeyManagerFactory managerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
managerFactory.init(ks, keyPass);
return managerFactory.getKeyManagers();
}
/**
* Create and return TrustManager(s) using the passed information.
*
* If trustStoreType is null, the JSSE default keystore type is used.
* No other arguments may be null.
*
* If you want to use the Java default truststore, do not call this method
* at all. Just pass a null TrustManager[] as the trustManagers argument to
* buildSSLSocketFactory(...).
*
* @param trustStorePath Location of trust store on file system. Non-null.
* @param trustStorePass Password to decrypt truststore.
* @param trustStoreType JSSE TrustStore type. If null, default JSSE keystore type is used.
* @return TrustStore(s) created
* @throws IOException If the TrustStore cannot be read
* @throws GeneralSecurityException (subtypes of) for most crypto errors, password errors, etc.
*/
protected TrustManager[] createTrustManagers(String trustStorePath, char[] trustStorePass, String trustStoreType) throws IOException, GeneralSecurityException {
// Load the trust store
if (trustStoreType == null) {
trustStoreType = KeyStore.getDefaultType();
}
KeyStore trustKs = KeyStore.getInstance(trustStoreType);
FileInputStream fInTrustStore = new FileInputStream(trustStorePath);
try {
trustKs.load(fInTrustStore, trustStorePass);
} finally {
try {
fInTrustStore.close();
} catch (IOException ex) {
// Ignored
}
}
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(trustKs);
return trustFactory.getTrustManagers();
}
/**
* If you wish to override SSL protocol selection, you may do so by
* overriding this method. The defualt simply returns
* AbstractCertAuthFactory.DEFAULT_SSL_PROTOCOL_NAME .
* .
* @return JSSE ssl/tls protocol name AbstractCertAuthFactory.DEFAULT_SSL_PROTOCOL_NAME
*/
protected String getSSLProtocolName() {
return DEFAULT_SSL_PROTOCOL_NAME;
}
}