diff --git a/src/main/java/com/ibm/as400/access/AS400.java b/src/main/java/com/ibm/as400/access/AS400.java index cb208bf8..a50336a7 100644 --- a/src/main/java/com/ibm/as400/access/AS400.java +++ b/src/main/java/com/ibm/as400/access/AS400.java @@ -33,6 +33,8 @@ import java.util.TimeZone; import java.util.Vector; +import javax.net.ssl.SSLSocketFactory; + import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSManager; @@ -6071,4 +6073,18 @@ public void setEnabledCipherSuites(String[] suites) // ======== END ================= // Previous chunk of code moved from SecureAS400 // ======== END ================= + + /** + * Set the {@link SSLSocketFactory} that will be used when making secure connections. + *

+ * Note:An exception will be thrown if the AS400 object is not an instance of SecureAS400. + * + * @param sslSocketFactory the {@link SSLSocketFactory} to use + */ + public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) + { + ensureSecureInstance(); + + useSSLConnection_.sslSocketFactory_ = sslSocketFactory; + } } diff --git a/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java b/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java index defb9baa..94c429e1 100644 --- a/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java +++ b/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java @@ -25,6 +25,9 @@ import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.util.logging.Logger; + +import javax.net.ssl.SSLSocketFactory; + /* endif */ import java.util.Properties; import java.util.MissingResourceException; @@ -123,6 +126,8 @@ public class AS400JDBCDriver static final String DATABASE_PRODUCT_NAME_ = "DB2 UDB for AS/400"; // @D0A static final String DRIVER_NAME_ = "AS/400 Toolbox for Java JDBC Driver"; // @D0C @C5C @C6C static final String DRIVER_LEVEL_ = Copyright.DRIVER_LEVEL; + + public static final String PROPERTY_SSL_SOCKET_FACTORY = "property.ssl-socket-factory"; /* ifdef JDBC40 */ public static final int JDBC_MAJOR_VERSION_ = 4; // JDBC spec version: 4.0 @@ -1186,6 +1191,10 @@ else if (clearPassword == null) as400 = AS400.newInstance(secure, serverName, userName); else as400 = AS400.newInstance(secure, serverName, userName, clearPassword, additionalAuthenticationFactor); + SSLSocketFactory sslSocketFactoryObject = jdProperties.getCustomSSLSocketFactory(); + if (null != sslSocketFactoryObject) { + as400.setSSLSocketFactory(sslSocketFactoryObject); + } } catch (AS400SecurityException e) { diff --git a/src/main/java/com/ibm/as400/access/JDProperties.java b/src/main/java/com/ibm/as400/access/JDProperties.java index 15cd719b..8c847ed5 100644 --- a/src/main/java/com/ibm/as400/access/JDProperties.java +++ b/src/main/java/com/ibm/as400/access/JDProperties.java @@ -14,12 +14,25 @@ package com.ibm.as400.access; import java.io.Serializable; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.io.FileInputStream; import java.io.IOException; // @W2a import java.sql.DriverPropertyInfo; import java.util.Enumeration; import java.util.Properties; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + /** @@ -181,9 +194,12 @@ public class JDProperties implements Serializable, Cloneable //@PDC 550 static final int ADDITIONAL_AUTHENTICATION_FACTOR=101; static final int STAY_ALIVE = 102; + static final int TLS_TRUSTSTORE_FILE = 103; + static final int TLS_TRUSTSTORE_FILE_PASS = 104; + // @W2 always add to the end of the array! - private static final int NUMBER_OF_ATTRIBUTES_ = 103; + private static final int NUMBER_OF_ATTRIBUTES_ = 105; // Property names. @@ -254,6 +270,8 @@ public class JDProperties implements Serializable, Cloneable //@PDC 550 private static final String TIME_FORMAT_ = "time format"; private static final String TIMESTAMP_FORMAT_ = "timestamp format"; private static final String TIME_SEPARATOR_ = "time separator"; + private static final String TLS_TRUSTSTORE_FILE_ = "tls truststore"; + private static final String TLS_TRUSTSTORE_FILE_PASS_ = "tls truststore password"; private static final String TRACE_ = "trace"; private static final String TRACE_SERVER_ = "server trace"; // @j1a private static final String TRACE_TOOLBOX_ = "toolbox trace"; // @K1A @@ -1652,8 +1670,22 @@ public class JDProperties implements Serializable, Cloneable //@PDC 550 dpi_[i].required = false; dpi_[i].choices = new String[0]; defaults_[i] = "0"; + + i = TLS_TRUSTSTORE_FILE; + dpi_[i] = new DriverPropertyInfo (TLS_TRUSTSTORE_FILE_, ""); + dpi_[i].description = "TLS_TRUSTSTORE_FILE"; + dpi_[i].required = false; + dpi_[i].choices = new String[0]; + defaults_[i] = EMPTY_; + i = TLS_TRUSTSTORE_FILE_PASS; + dpi_[i] = new DriverPropertyInfo (TLS_TRUSTSTORE_FILE_PASS_, ""); + dpi_[i].description = "TLS_TRUSTSTORE_FILE_PASS"; + dpi_[i].required = false; + dpi_[i].choices = new String[0]; + defaults_[i] = EMPTY_; + } @@ -2019,6 +2051,111 @@ String getString (int index) return value.trim(); } + /** + * Gets a custom SSL Socket Factory, or returns null if no custom SSL Socket factory is specified + * (in which case, the system default factory will be used). + * + * The custom SSL Socket Factory is determined as follows: + *

+ */ + SSLSocketFactory getCustomSSLSocketFactory() { + Properties originalProps = this.getOriginalInfo(); + Object sslSocketFactoryObject = null == originalProps ? null: originalProps.get(AS400JDBCDriver.PROPERTY_SSL_SOCKET_FACTORY); + if ((sslSocketFactoryObject != null) && (sslSocketFactoryObject instanceof SSLSocketFactory)) { + return (SSLSocketFactory) sslSocketFactoryObject; + } + final String truststoreFile = getString(TLS_TRUSTSTORE_FILE); + final String truststorePass = getString(TLS_TRUSTSTORE_FILE_PASS); + if (null != truststoreFile && !truststoreFile.isEmpty()) { + return new SSLSocketFactory() { + private SSLSocketFactory sslSocketFactory_ = null; + + private synchronized SSLSocketFactory getSSLSocketFactory() throws IOException { + if (null != sslSocketFactory_) { + return sslSocketFactory_; + } + if ("*ANY".equalsIgnoreCase(truststoreFile) && "*ANY".equalsIgnoreCase(truststorePass)) { + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + //@formatter:off + ctx.init(null, new TrustManager[] { + new X509TrustManager() { + @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } + @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } + @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + } + }, null); + //@formatter:on + return sslSocketFactory_ = ctx.getSocketFactory(); + } catch (Exception e) { + throw e instanceof IOException ? (IOException) e : new IOException(e); + } + } + try (FileInputStream trustFile = new FileInputStream(truststoreFile)) { + KeyStore myTrustStore = KeyStore.getInstance("JKS"); + myTrustStore.load(trustFile, null == truststorePass ? null :truststorePass.toCharArray()); + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(myTrustStore); + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(null, trustManagerFactory.getTrustManagers(), null); + return sslSocketFactory_ = ctx.getSocketFactory(); + } catch (Exception e) { + throw e instanceof IOException ? (IOException) e : new IOException(e); + } + } + + //@formatter:off + @Override + public String[] getDefaultCipherSuites() { + try { return getSSLSocketFactory().getDefaultCipherSuites();} catch (Exception e) { } + return ((SSLSocketFactory) SSLSocketFactory.getDefault()).getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + try { return getSSLSocketFactory().getSupportedCipherSuites(); } catch (Exception e) { } + return ((SSLSocketFactory) SSLSocketFactory.getDefault()).getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return getSSLSocketFactory().createSocket(s, host, port, autoClose); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return getSSLSocketFactory().createSocket(host, port); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { + return getSSLSocketFactory().createSocket(host, port, localHost, localPort); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return getSSLSocketFactory().createSocket(host, port); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException { + return getSSLSocketFactory().createSocket(address, port, localAddress, localPort); + } + //@formatter:on + + }; + } + return null; + } + /** * Get the clear password. The caller is responsible for clearing the array * after it is done with the password diff --git a/src/main/java/com/ibm/as400/access/PortMapper.java b/src/main/java/com/ibm/as400/access/PortMapper.java index 046b6e0c..7ee3daed 100644 --- a/src/main/java/com/ibm/as400/access/PortMapper.java +++ b/src/main/java/com/ibm/as400/access/PortMapper.java @@ -220,7 +220,7 @@ static SocketContainer getServerSocket(String systemName, if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Starting a secure socket to " + serviceName); { // JSSE is supported since v5r4. sc = (SocketContainer)AS400.loadImpl("com.ibm.as400.access.SocketContainerJSSE"); - sc.setProperties(socket, null, systemName, srvPort, null); + sc.setProperties(socket, null, systemName, srvPort, useSSL); } } else diff --git a/src/main/java/com/ibm/as400/access/SSLOptions.java b/src/main/java/com/ibm/as400/access/SSLOptions.java index 1173c9d9..9f341152 100644 --- a/src/main/java/com/ibm/as400/access/SSLOptions.java +++ b/src/main/java/com/ibm/as400/access/SSLOptions.java @@ -15,6 +15,8 @@ import java.io.Serializable; +import javax.net.ssl.SSLSocketFactory; + // Class to move SSL configuration options from proxy client to proxy server. class SSLOptions implements Serializable { @@ -47,4 +49,5 @@ class SSLOptions implements Serializable int proxyEncryptionMode_ = SecureAS400.CLIENT_TO_SERVER; // Sslight removed boolean useSslight_ = false; + SSLSocketFactory sslSocketFactory_ = null; } diff --git a/src/main/java/com/ibm/as400/access/SocketContainerJSSE.java b/src/main/java/com/ibm/as400/access/SocketContainerJSSE.java index ae457fac..140ebb5d 100644 --- a/src/main/java/com/ibm/as400/access/SocketContainerJSSE.java +++ b/src/main/java/com/ibm/as400/access/SocketContainerJSSE.java @@ -32,7 +32,7 @@ void setProperties(Socket socket, String serviceName, String systemName, int por { if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "SocketContainerJSSE: create SSLSocket"); - SSLSocketFactory sslFactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); + SSLSocketFactory sslFactory = ((options != null) && (options.sslSocketFactory_ != null)) ? options.sslSocketFactory_ : (SSLSocketFactory)SSLSocketFactory.getDefault(); sslSocket_ = (SSLSocket)sslFactory.createSocket(socket, systemName, port, true); //@P4A START if(SecureAS400.changeCipherSuites)