All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazon.redshift.ssl.LibPQFactory Maven / Gradle / Ivy

There is a newer version: 2.1.0.30
Show newest version
/*
 * 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
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy