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:
+ *
+ * - If an {@link SSLSocketFactory} object was provided through the special property defined by
+ * {@link AS400JDBCDriver#PROPERTY_SSL_SOCKET_FACTORY}, all other properties are ignored and
+ * that object is returned.
+ *
- A {@link SSLSocketFactory} will be created if both the {@value #TLS_TRUSTSTORE_FILE_} {@value #TLS_TRUSTSTORE_FILE_PASS_}
+ * properties were specified, indicating a JKS-format truststore file and password. Note that the special value '*ANY'
+ * can be used to disable all verification.
+ *
+ */
+ 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)