Skip to content

Commit

Permalink
Security enhancement for the JTOpen library (#200)
Browse files Browse the repository at this point in the history
Signed-off-by: Marcel Romijn <[email protected]>
Signed-off-by: Jesse Gorzinski <[email protected]>
Co-authored-by: Jesse Gorzinski <[email protected]>
  • Loading branch information
MarcelRomijn and ThePrez authored Jan 27, 2025
1 parent 189d813 commit 1267443
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 3 deletions.
16 changes: 16 additions & 0 deletions src/main/java/com/ibm/as400/access/AS400.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
* <p>
* <b>Note:</b>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;
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/ibm/as400/access/AS400JDBCDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
{
Expand Down
139 changes: 138 additions & 1 deletion src/main/java/com/ibm/as400/access/JDProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;



/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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_;

}


Expand Down Expand Up @@ -2019,6 +2051,111 @@ String getString (int index)
return value.trim();
}

/**
* Gets a custom SSL Socket Factory, or returns <tt>null</tt> 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:
* <ul>
* <li>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.
* <li>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.
* </ul>
*/
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
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/ibm/as400/access/PortMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/ibm/as400/access/SSLOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -47,4 +49,5 @@ class SSLOptions implements Serializable
int proxyEncryptionMode_ = SecureAS400.CLIENT_TO_SERVER;
// Sslight removed
boolean useSslight_ = false;
SSLSocketFactory sslSocketFactory_ = null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 1267443

Please sign in to comment.