com.amazon.redshift.ssl.LibPQFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redshift-jdbc42 Show documentation
Show all versions of redshift-jdbc42 Show documentation
Java JDBC 4.2 (JRE 8+) driver for Redshift database
/*
* Copyright (c) 2004, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package com.amazon.redshift.ssl;
import com.amazon.redshift.RedshiftProperty;
import com.amazon.redshift.jdbc.SslMode;
import com.amazon.redshift.ssl.NonValidatingFactory.NonValidatingTM;
import com.amazon.redshift.util.GT;
import com.amazon.redshift.util.ObjectFactory;
import com.amazon.redshift.util.RedshiftException;
import com.amazon.redshift.util.RedshiftState;
import java.io.Console;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Properties;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
/**
* Provide an SSLSocketFactory that is compatible with the libpq behaviour.
*/
public class LibPQFactory extends WrappedFactory {
/**
* The system property to set/get the trustore path.
*/
private static final String TRUSTSTORE_PROPERTY = "javax.net.ssl.trustStore";
/**
* The system property to set/get the trustore passphrase.
*/
private static final String TRUSTSTORE_PWD_PROPERTY ="javax.net.ssl.trustStorePassword";
KeyManager km;
boolean defaultfile;
private CallbackHandler getCallbackHandler(Properties info) throws RedshiftException {
// Determine the callback handler
CallbackHandler cbh;
String sslpasswordcallback = RedshiftProperty.SSL_PASSWORD_CALLBACK.get(info);
if (sslpasswordcallback != null) {
try {
cbh = ObjectFactory.instantiate(CallbackHandler.class,sslpasswordcallback, info, false, null);
} catch (Exception e) {
throw new RedshiftException(
GT.tr("The password callback class provided {0} could not be instantiated.",
sslpasswordcallback),
RedshiftState.CONNECTION_FAILURE, e);
}
} else {
cbh = new ConsoleCallbackHandler(RedshiftProperty.SSL_PASSWORD.get(info));
}
return cbh;
}
private void initPk8(String sslkeyfile, String defaultdir, Properties info) throws RedshiftException {
// Load the client's certificate and key
String sslcertfile = RedshiftProperty.SSL_CERT.get(info);
if (sslcertfile == null) { // Fall back to default
defaultfile = true;
sslcertfile = defaultdir + "redshift.crt";
}
// If the properties are empty, give null to prevent client key selection
km = new LazyKeyManager(("".equals(sslcertfile) ? null : sslcertfile),
("".equals(sslkeyfile) ? null : sslkeyfile), getCallbackHandler(info), defaultfile);
}
private void initP12(String sslkeyfile, Properties info) throws RedshiftException {
km = new PKCS12KeyManager(sslkeyfile, getCallbackHandler(info));
}
/**
* @param info the connection parameters The following parameters are used:
* sslmode,sslcert,sslkey,sslrootcert,sslhostnameverifier,sslpasswordcallback,sslpassword
* @throws RedshiftException if security error appears when initializing factory
*/
public LibPQFactory(Properties info) throws RedshiftException {
try {
SSLContext ctx = SSLContext.getInstance("TLS"); // or "SSL" ?
// Determining the default file location
String pathsep = System.getProperty("file.separator");
String defaultdir;
if (System.getProperty("os.name").toLowerCase().contains("windows")) { // It is Windows
defaultdir = System.getenv("APPDATA") + pathsep + "redshift" + pathsep;
} else {
defaultdir = System.getProperty("user.home") + pathsep + ".redshift" + pathsep;
}
String sslkeyfile = RedshiftProperty.SSL_KEY.get(info);
if (sslkeyfile == null) { // Fall back to default
defaultfile = true;
sslkeyfile = defaultdir + "redshift.pk8";
}
if (sslkeyfile.endsWith("pk8")) {
initPk8(sslkeyfile, defaultdir, info);
}
if (sslkeyfile.endsWith("p12")) {
initP12(sslkeyfile, info);
}
TrustManager[] tm;
SslMode sslMode = SslMode.of(info);
if (!sslMode.verifyCertificate()) {
// server validation is not required
tm = new TrustManager[]{new NonValidatingTM()};
} else {
String sslTrustStorePath = RedshiftProperty.SSL_TRUSTSTORE_PATH_KEY.get(info);
String sslrootcertfile = RedshiftProperty.SSL_ROOT_CERT.get(info);
String sslTrustStorePwd = RedshiftProperty.SSL_TRUSTSTORE_PWD_KEY.get(info);
// Load the server certificate
if (null != sslTrustStorePath)
{
tm = getTrustManagerWithDefinedTrustStore(sslTrustStorePath, sslTrustStorePwd);
}
else if(null != sslrootcertfile)
{
tm = getTrustManagerWithImportedCertificate(sslrootcertfile);
}
else
{
tm = getDefaultTrustManager();
}
/* The original root.crt code start ---
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
KeyStore ks;
try {
ks = KeyStore.getInstance("jks");
} catch (KeyStoreException e) {
// this should never happen
throw new NoSuchAlgorithmException("jks KeyStore not available");
}
// String sslrootcertfile = RedshiftProperty.SSL_ROOT_CERT.get(info);
if (sslrootcertfile == null) { // Fall back to default
sslrootcertfile = defaultdir + "root.crt";
}
FileInputStream fis;
try {
fis = new FileInputStream(sslrootcertfile); // NOSONAR
} catch (FileNotFoundException ex) {
throw new RedshiftException(
GT.tr("Could not open SSL root certificate file {0}.", sslrootcertfile),
RedshiftState.CONNECTION_FAILURE, ex);
}
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// Certificate[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{}); //Does
// not work in java 1.4
Object[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{});
ks.load(null, null);
for (int i = 0; i < certs.length; i++) {
ks.setCertificateEntry("cert" + i, (Certificate) certs[i]);
}
tmf.init(ks);
} catch (IOException ioex) {
throw new RedshiftException(
GT.tr("Could not read SSL root certificate file {0}.", sslrootcertfile),
RedshiftState.CONNECTION_FAILURE, ioex);
} catch (GeneralSecurityException gsex) {
throw new RedshiftException(
GT.tr("Loading the SSL root certificate {0} into a TrustManager failed.",
sslrootcertfile),
RedshiftState.CONNECTION_FAILURE, gsex);
} finally {
try {
fis.close();
} catch (IOException e) {
// ignore
}
}
tm = tmf.getTrustManagers();
--- The original root.crt code end. */
}
// finally we can initialize the context
try {
ctx.init(new KeyManager[]{km}, tm, null);
} catch (KeyManagementException ex) {
throw new RedshiftException(GT.tr("Could not initialize SSL context."),
RedshiftState.CONNECTION_FAILURE, ex);
}
factory = ctx.getSocketFactory();
} catch (NoSuchAlgorithmException ex) {
throw new RedshiftException(GT.tr("Could not find a java cryptographic algorithm: {0}.",
ex.getMessage()), RedshiftState.CONNECTION_FAILURE, ex);
}
}
/**
* Propagates any exception from {@link LazyKeyManager}.
*
* @throws RedshiftException if there is an exception to propagate
*/
public void throwKeyManagerException() throws RedshiftException {
if (km != null) {
if (km instanceof LazyKeyManager) {
((LazyKeyManager)km).throwKeyManagerException();
}
if (km instanceof PKCS12KeyManager) {
((PKCS12KeyManager)km).throwKeyManagerException();
}
}
}
/**
* Initialize and return the trust manager with SSLtruststore passed in from the user
*
* @return TrustManager[] Array of initialized trust managers
* @throws RedshiftException
*/
private TrustManager[] getTrustManagerWithDefinedTrustStore(String sslTrustStorePath,
String sslTrustStorePwd) throws RedshiftException
{
// The Keystore containing certificates
KeyStore truststore = null;
// The input stream to read in the jks file
FileInputStream trustStoreSource = null;
try
{
trustStoreSource = new FileInputStream(sslTrustStorePath);
//Load the trust store using the trust store location provided
truststore = KeyStore.getInstance(KeyStore.getDefaultType());
truststore.load(
trustStoreSource,
sslTrustStorePwd != null ?
sslTrustStorePwd.toCharArray() : null);
// Initialize the TrustManagerFactory
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(truststore);
return tmf.getTrustManagers();
}
catch (Exception e)
{
// Error retrieving the available trust managers
throw new RedshiftException(
GT.tr("Error retrieving the available trust managers {0}.", sslTrustStorePath),
RedshiftState.CONNECTION_FAILURE, e);
}
finally
{
if (trustStoreSource != null)
{
try
{
trustStoreSource.close();
}
catch (IOException e)
{
// Ignore
}
}
}
}
/**
* Returns the trust managers for the temporary Truststore with the given certificate
* imported into the Truststore.
*
* @param keystorePath The path to the keystore.
* @param certificatePath The path to the certificate file. The file must be in either
* PEM or DER format.
*
* @throws RedshiftException If an error occurs.
*/
private TrustManager[] getTrustManagerWithImportedCertificate(String sslRootCert)
throws RedshiftException
{
KeyStore truststore = getDefaultKeystore();
try
{
Certificate[] chain = getCertificateChain(sslRootCert);
// Add the certificate to the truststore.
truststore.setCertificateEntry(sslRootCert, chain[0]);
}
catch (Exception e)
{
// Error loading the certificate file.
throw new RedshiftException(
GT.tr("Error loading the certificate file {0}.", sslRootCert),
RedshiftState.CONNECTION_FAILURE, e);
}
return getTrustManager(truststore);
}
/**
* Returns the KeyStore for the given external path.
*
* @throws RedshiftException If an error occurs.
*
*/
private KeyStore getDefaultKeystore() throws RedshiftException
{
InputStream keystoreStream = null;
String passphrase = null;
String keystorePath = System.getProperty(TRUSTSTORE_PROPERTY);
passphrase = System.getProperty(TRUSTSTORE_PWD_PROPERTY);
if (null == keystorePath)
{
// Default keystore : ${JAVA_HOME}/lib/security/cacerts
StringBuilder trustorePath = new StringBuilder();
trustorePath.append(System.getProperty("java.home"));
trustorePath.append(File.separatorChar);
trustorePath.append("lib");
trustorePath.append(File.separatorChar);
trustorePath.append("security");
trustorePath.append(File.separatorChar);
trustorePath.append("cacerts");
keystorePath = trustorePath.toString();
}
try
{
keystoreStream = new FileInputStream(new File(keystorePath));
}
catch (Exception e)
{
// Error retrieving the available trust managers
throw new RedshiftException(
GT.tr("Error loading the keystore {0}.", keystorePath),
RedshiftState.CONNECTION_FAILURE, e);
}
try
{
// Load the keystore
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] passphraseArray = null;
if (null != passphrase)
{
passphraseArray = passphrase.toCharArray();
}
keystore.load(keystoreStream, passphraseArray);
keystoreStream.close();
loadDefaultCA(keystore, "redshift.crt");
loadDefaultCA(keystore, "bjs.redshift.crt");
loadDefaultCA(keystore, "pdt.redshift.crt");
return keystore;
}
catch (Exception e)
{
return fallbackKeyStores(keystorePath, passphrase, e);
}
}
/*
* BouncyCastle is very strict about key store type. JDK8 has default type JKS,
* while JDK11 has default type PKCS12.
* So JDK11 with BouncyCastle provider throws error - java.io.IOException: stream does not represent a PKCS12 key store.
* This fallback mechanism fix the issue as it tries different key store types.
*/
private KeyStore fallbackKeyStores(String keystorePath,
String passphrase,
Exception originalEx) throws RedshiftException
{
String[] keystoreTypes = {"JKS", "PKCS12", "JCEKS"};
for(String keystoreType:keystoreTypes)
{
try
{
InputStream keystoreStream = null;
try
{
keystoreStream = new FileInputStream(new File(keystorePath));
}
catch (Exception e)
{
// Error retrieving the available trust managers
throw new RedshiftException(
GT.tr("Error loading the keystore {0}.", keystorePath),
RedshiftState.CONNECTION_FAILURE, e);
}
// Load the keystore
KeyStore keystore = KeyStore.getInstance(keystoreType);
char[] passphraseArray = null;
if (null != passphrase)
{
passphraseArray = passphrase.toCharArray();
}
keystore.load(keystoreStream, passphraseArray);
keystoreStream.close();
loadDefaultCA(keystore, "redshift.crt");
loadDefaultCA(keystore, "bjs.redshift.crt");
loadDefaultCA(keystore, "pdt.redshift.crt");
return keystore;
}
catch (RedshiftException rsex)
{
// inner exception of stream, propagate to caller
throw rsex;
}
catch (Exception e)
{
// Ignore and try another keystore type in the loop.
}
} // Loop
// Error retrieving the available trust managers
throw new RedshiftException(
GT.tr("Error loading the provided keystore."),
RedshiftState.CONNECTION_FAILURE, originalEx);
}
/**
* Loads the certificate into the keystore.
*
* @param keystore The keystore.
* @param name The name of the certificate.
* @throws IOException When the file is not found.
* @throws GeneralSecurityException
*/
private void loadDefaultCA(KeyStore keystore, String name)
throws IOException, GeneralSecurityException
{
InputStream is = null;
try
{
is = NonValidatingFactory.class.getResourceAsStream(name);
if (is == null)
{
return;
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(is);
keystore.setCertificateEntry(name, cert);
}
finally
{
if (is != null)
{
is.close();
}
}
}
/**
* Returns a certificate chain with the certificate found at certificatePath added to it.
*
* @param certificatePath The path to the certificate.
*
* @throws RedshiftException If an error occurs.
*/
private Certificate[] getCertificateChain(String certificatePath)
throws RedshiftException
{
Certificate[] chain = {};
try
{
File certificateFile = new File(certificatePath);
if (!certificateFile.isFile() || !certificateFile.exists())
{
throw new RedshiftException(
GT.tr("Error certificate file doesn't found {0}.", certificatePath),
RedshiftState.CONNECTION_FAILURE);
}
InputStream certificateStream = new FileInputStream(certificateFile);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
chain = certificateFactory.generateCertificates(certificateStream).toArray(chain);
certificateStream.close();
if (0 >= chain.length || null == chain[0])
{
throw new RedshiftException(
GT.tr("Error missing certificate."),
RedshiftState.CONNECTION_FAILURE);
}
}
catch (Exception e)
{
throw new RedshiftException(
GT.tr("Error loading certificate chain."),
RedshiftState.CONNECTION_FAILURE, e);
}
return chain;
}
/**
* Returns the trust managers for the given external Truststore.
*
* @param keystore The keystore
*
* @throws RedshiftException If an error occurs.
*
*/
private TrustManager[] getTrustManager(KeyStore keystore)
throws RedshiftException
{
try
{
// Initialize the TrustManagerFactory
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keystore);
return tmf.getTrustManagers();
}
catch (Exception e)
{
// Error retrieving the available trust managers
throw new RedshiftException(
GT.tr("Error retrieving the available trust managers."),
RedshiftState.CONNECTION_FAILURE, e);
}
}
/**
* Returns the trust managers for the trustore.
*
* @param keystoreStream The keystore stream.
* @param passphrase The keystore passphrase.
*
* @throws RedshiftException If an error occurs.
*
*/
private TrustManager[] getDefaultTrustManager()
throws RedshiftException
{
KeyStore keystore = getDefaultKeystore();
return getTrustManager(keystore);
}
/**
* A CallbackHandler that reads the password from the console or returns the password given to its
* constructor.
*/
public static class ConsoleCallbackHandler implements CallbackHandler {
private char[] password = null;
ConsoleCallbackHandler(String password) {
if (password != null) {
this.password = password.toCharArray();
}
}
/**
* Handles the callbacks.
*
* @param callbacks The callbacks to handle
* @throws UnsupportedCallbackException If the console is not available or other than
* PasswordCallback is supplied
*/
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
Console cons = System.console();
if (cons == null && password == null) {
throw new UnsupportedCallbackException(callbacks[0], "Console is not available");
}
for (Callback callback : callbacks) {
if (!(callback instanceof PasswordCallback)) {
throw new UnsupportedCallbackException(callback);
}
PasswordCallback pwdCallback = (PasswordCallback) callback;
if (password != null) {
pwdCallback.setPassword(password);
continue;
}
// It is used instead of cons.readPassword(prompt), because the prompt may contain '%'
// characters
pwdCallback.setPassword(cons.readPassword("%s", pwdCallback.getPrompt()));
}
}
} // ConsoleCallbackHandler class
}