So I've been helping a friend debug and resolve a problem whereby his Java 7 code wasn't connecting to a DB2 database via a TLS 1.2 connection.
I checked the version of the JDBC driver that I was using ( this was shipped with IBM ODM Rules 8.9, which is what I happen to using for my tests, even though this is ONLY Java to DB2 testing ): -
import java.lang.*;
import java.sql.*;
import java.io.*;
class DB2connect_ssl2
{
public static void main(String argv[])
{
try
{
Connection con = null;
PreparedStatement pstmt = null;
try
{
Class.forName("com.ibm.db2.jcc.DB2Driver").newInstance();
con = DriverManager.getConnection( "jdbc:db2://odm.uk.ibm.com:60007/SAMPLE" +
":user=db2inst1;password=passw0rd;" +
"sslConnection=true;sslTrustStoreLocation=/home/wasadmin/davehay.jks;sslTrustStorePassword=davehay;" +
"keepAliveTimeout=10;" +
"traceDirectory=/tmp;" +
"traceFile=foobar.trc;" +
"traceFileAppend=false;" +
"traceLevel=-1;" );
System.out.println(" Connected to database with type 4 url") ;
}
catch (Throwable e)
{
System.out.println("Connect failed" + e);
}
try
{
pstmt = con.prepareStatement("select * from DB2INST1.EMP");
}
catch (Throwable e)
{
System.out.println("Statement Prepare failed" + e);
}
try
{
ResultSet s = pstmt.executeQuery();
while (s.next())
{
String v_id = s.getString(1);
String v_stream_id = s.getString(2);
System.out.println( v_id + "" + v_stream_id );
}
s.close();
}
catch (Throwable e)
{
System.out.println("fetch processing failed");
}
con.close();
}
catch (Exception e)
{
System.out.println("Connect failed" + e);
}
}
}
import java.sql.Connection ;
import java.sql.DriverManager ;
import java.sql.ResultSet ;
import java.sql.Statement ;
import java.sql.SQLException;
import org.omg.CORBA.VersionSpecHelper;
class JdbcTestDB2
{
public static void main (String args[])
{
try
{
Class.forName("com.ibm.db2.jcc.DB2Driver");
}
catch (ClassNotFoundException e)
{
System.err.println (e) ;
System.exit (-1) ;
}
String hostname = "odm.uk.ibm.com";
int port = 60007;
String dbName = "SAMPLE";
String userName = "db2inst1";
String password = "passw0rd";
String sslConnection = "true";
java.util.Properties properties = new java.util.Properties();
properties.put("user",userName);
properties.put("password", password);
properties.put("sslConnection", sslConnection);
properties.put("sslTrustStoreLocation","/home/wasadmin/davehay.jks");
properties.put("sslTrustStorePassword","davehay");
String url = "jdbc:db2://" + hostname + ":" + port + "/" + dbName;
try
{
Connection connection = DriverManager.getConnection(url,properties);
String query = "select EMPNO,FIRSTNME,LASTNAME from DB2INST1.EMPLOYEE" ;
Statement statement = connection.createStatement () ;
ResultSet rs = statement.executeQuery (query) ;
while ( rs.next () )
System.out.println (rs.getString (1) + "" + rs.getString(2) + "" + rs.getString(3)) ;
connection.close () ;
}
catch (java.sql.SQLException e)
{
System.err.println (e) ;
System.exit (-1) ;
}
}
}
To validate this, I setup a DB2 instance to support TLS 1.2: -
As root
echo "db2c_ssl 60007/tcp">> /etc/services
As db2inst1
/home/db2inst1/sqllib/gskit/bin/gsk8capicmd_64 -keydb -create -db /home/db2inst1/keystore.kdb -pw passw0rd -stash
/home/db2inst1/sqllib/gskit/bin/gsk8capicmd_64 -cert -create -db /home/db2inst1/keystore.kdb -pw passw0rd -label "odm.uk.ibm.com" -dn "cn=odm.uk.ibm.com,dc=uk,dc=ibm,dc=com"
db2 update dbm cfg using SSL_SVR_KEYDB /home/db2inst1/keystore.kdb
db2 update dbm cfg using SSL_SVR_STASH /home/db2inst1/keystore.sth
db2 update dbm cfg using SSL_SVR_LABEL odm.uk.ibm.com
db2 update dbm cfg using SSL_SVCENAME db2c_ssl
db2 update dbm cfg using SSL_VERSIONS TLSV12
db2 update dbm cfg using SSL_CIPHERSPECS TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
db2set DB2COMM=TCPIP,SSL
db2stop
db2start
As db2inst1
/home/db2inst1/sqllib/gskit/bin/gsk8capicmd_64 -keydb -create -db /home/db2inst1/keystore.kdb -pw passw0rd -stash
/home/db2inst1/sqllib/gskit/bin/gsk8capicmd_64 -cert -create -db /home/db2inst1/keystore.kdb -pw passw0rd -label "odm.uk.ibm.com" -dn "cn=odm.uk.ibm.com,dc=uk,dc=ibm,dc=com"
db2 update dbm cfg using SSL_SVR_KEYDB /home/db2inst1/keystore.kdb
db2 update dbm cfg using SSL_SVR_STASH /home/db2inst1/keystore.sth
db2 update dbm cfg using SSL_SVR_LABEL odm.uk.ibm.com
db2 update dbm cfg using SSL_SVCENAME db2c_ssl
db2 update dbm cfg using SSL_VERSIONS TLSV12
db2 update dbm cfg using SSL_CIPHERSPECS TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
db2set DB2COMM=TCPIP,SSL
db2stop
db2start
and then validated what the WAS box was seeing: -
…
New, TLSv1/SSLv3, Cipher is AES256-GCM-SHA384
Server public key is 1024 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : AES256-GCM-SHA384
…
Server public key is 1024 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : AES256-GCM-SHA384
…
Having relieved the DB2 self-signed certificate: -
openssl s_client -showcerts -connect odm.uk.ibm.com:60007</dev/null | openssl x509 -outform DER > ~/db2.cer
and created a JKS trust store: -
/opt/ibm/WebSphere/AppServer/java/jre/bin/keytool -import -file ~/db2.cer -keystore ~/davehay.jks -alias DB2 -storepass davehay
and created a JKS trust store: -
/opt/ibm/WebSphere/AppServer/java/jre/bin/keytool -import -file ~/db2.cer -keystore ~/davehay.jks -alias DB2 -storepass davehay
I then tested my Java code ( see below ) : -
java -cp /opt/ibm/WebSphere/AppServer/ODMjdbcdrivers/DB2/db2jcc.jar:/home/wasadmin DB2connect_ssl2
Instead of working, this generated a trace file ( as per the code ): -
view /tmp/foobar.trc_sds_1
which contained: -
...
[jcc] BEGIN TRACE_DIAGNOSTICS
[jcc][Thread:main][SQLException@7807b259] java.sql.SQLException
[jcc][Thread:main][SQLException@7807b259] SQL state = 08001
[jcc][Thread:main][SQLException@7807b259] Error code = -4499
[jcc][Thread:main][SQLException@7807b259] Message = [jcc][t4][2030][11211][3.61.65] A communication error occurred during operations on the connection's underlying socket, socket input stream,
or socket output stream. Error location: T4Agent.sendRequest() - flush (-2). Message: Received fatal alert: handshake_failure. ERRORCODE=-4499, SQLSTATE=08001
[jcc][Thread:main][SQLException@7807b259] Stack trace follows
com.ibm.db2.jcc.am.DisconnectNonTransientConnectionException: [jcc][t4][2030][11211][3.61.65] A communication error occurred during operations on the connection's underlying socket, socket input stream,
or socket output stream. Error location: T4Agent.sendRequest() - flush (-2). Message: Received fatal alert: handshake_failure. ERRORCODE=-4499, SQLSTATE=08001
at com.ibm.db2.jcc.am.ed.a(ed.java:319)
...
[jcc] BEGIN TRACE_DIAGNOSTICS
[jcc][Thread:main][SQLException@7807b259] java.sql.SQLException
[jcc][Thread:main][SQLException@7807b259] SQL state = 08001
[jcc][Thread:main][SQLException@7807b259] Error code = -4499
[jcc][Thread:main][SQLException@7807b259] Message = [jcc][t4][2030][11211][3.61.65] A communication error occurred during operations on the connection's underlying socket, socket input stream,
or socket output stream. Error location: T4Agent.sendRequest() - flush (-2). Message: Received fatal alert: handshake_failure. ERRORCODE=-4499, SQLSTATE=08001
[jcc][Thread:main][SQLException@7807b259] Stack trace follows
com.ibm.db2.jcc.am.DisconnectNonTransientConnectionException: [jcc][t4][2030][11211][3.61.65] A communication error occurred during operations on the connection's underlying socket, socket input stream,
or socket output stream. Error location: T4Agent.sendRequest() - flush (-2). Message: Received fatal alert: handshake_failure. ERRORCODE=-4499, SQLSTATE=08001
at com.ibm.db2.jcc.am.ed.a(ed.java:319)
...
I turned on debugging: -
java -cp /opt/ibm/WebSphere/AppServer/ODMjdbcdrivers/DB2/db2jcc.jar:/home/wasadmin -Djavax.net.debug=ssl DB2connect_ssl2
which told me what I already knew: -
…
main, WRITE: TLSv1 Handshake, length = 123
main, READ: TLSv1.2 Alert, length = 2
main, RECV TLSv1 ALERT: fatal, handshake_failure
main, called closeSocket()
main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
main, READ: TLSv1.2 Alert, length = 2
main, RECV TLSv1 ALERT: fatal, handshake_failure
main, called closeSocket()
main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
…
Similarly, DB2 told me that things were awry: -
db2diag -f
which returned: -
…
2017-08-18-19.27.52.683893+060 I48627542E482 LEVEL: Error
PID : 128279 TID : 140419925010176 PROC : db2sysc 0
INSTANCE: db2inst1 NODE : 000
APPHDL : 0-24873
HOSTNAME: odm.uk.ibm.com
EDUID : 23 EDUNAME: db2agent () 0
FUNCTION: DB2 UDB, common communication, sqlccMapSSLErrorToDB2Error, probe:30
MESSAGE : DIA3604E The SSL function "gsk_secure_soc_init" failed with the
return code "402" in "sqlccSSLSocketSetup".
PID : 128279 TID : 140419925010176 PROC : db2sysc 0
INSTANCE: db2inst1 NODE : 000
APPHDL : 0-24873
HOSTNAME: odm.uk.ibm.com
EDUID : 23 EDUNAME: db2agent () 0
FUNCTION: DB2 UDB, common communication, sqlccMapSSLErrorToDB2Error, probe:30
MESSAGE : DIA3604E The SSL function "gsk_secure_soc_init" failed with the
return code "402" in "sqlccSSLSocketSetup".
…
I've seen this before: -
However, this time around the solution was … different.
I experimented with various tweaks to the Java command-line: -
java -cp /opt/ibm/WebSphere/AppServer/ODMjdbcdrivers/DB2/db2jcc.jar:/home/wasadmin -Dcom.ibm.jsse2.overrideDefaultProtocol=TLSv12 DB2connect_ssl2
and: -
java -cp /opt/ibm/WebSphere/AppServer/ODMjdbcdrivers/DB2/db2jcc.jar:/home/wasadmin -Dcom.ibm.jsse2.overrideDefaultProtocol=TLSv12-Dhttps.cipherSuites=TLS_RSA_WITH_AES_256_GCM_SHA384 DB2connect_ssl2
etc. but to no avail.
I'd also verified that my JRE was set to support the stronger export-grade ciphers: -
java -cp /home/wasadmin/ CipherTest
PASSED: Max AES key length OK! - >= 256 (2147483647).
At which point, I was scratching my head ….
… and then I read this: -
which said, in part, this: -
...
3. Identify your JCC driver version by issuing the following command:
java -cp db2jcc4.jar com.ibm.db2.jcc.DB2Jcc -version
4. Determine if you need to update your JCC driver.
3. Identify your JCC driver version by issuing the following command:
java -cp db2jcc4.jar com.ibm.db2.jcc.DB2Jcc -version
4. Determine if you need to update your JCC driver.
• If you use Java 8 and an IBM JDK, the minimum recommended level is 4.18.
• Otherwise, update to version 4.22.29 or higher. This is the version that shipped with DB2 v11.1 FP1.
You can download a new JCC driver here. If you deploy your application in a Bluemix environment, either remove the JCC driver ( db2jcc4.jar) in the application's WebContent/WEB-INF/lib/ folder or else ensure that the driver in this folder is up to date before you build and deploy the application. ...
Given that I'm using DB2 11.1, as per this: -
db2level
DB21085I This instance or install (instance name, where applicable:
"db2inst1") uses "64" bits and DB2 code release "SQL11012" with level
identifier "0203010F".
Informational tokens are "DB2 v11.1.2.2", "s1706091900", "DYN1701310100AMD64",
and Fix Pack "2".
Product is installed at "/opt/ibm/db2/V11.1".
"db2inst1") uses "64" bits and DB2 code release "SQL11012" with level
identifier "0203010F".
Informational tokens are "DB2 v11.1.2.2", "s1706091900", "DYN1701310100AMD64",
and Fix Pack "2".
Product is installed at "/opt/ibm/db2/V11.1".
I checked the version of the JDBC driver that I was using ( this was shipped with IBM ODM Rules 8.9, which is what I happen to using for my tests, even though this is ONLY Java to DB2 testing ): -
java -cp /opt/ibm/WebSphere/AppServer/ODMjdbcdrivers/DB2/db2jcc.jar com.ibm.db2.jcc.DB2Jcc -version
IBM DB2 JDBC Universal Driver Architecture 3.61.65
java -cp /opt/ibm/WebSphere/AppServer/ODMjdbcdrivers/DB2/db2jcc4.jar com.ibm.db2.jcc.DB2Jcc -version
IBM Data Server Driver for JDBC and SQLJ 4.11.69
which is below the recommended version 4.22.29.
Therefore, I checked the versions that ship with DB2 itself: -
java -cp /opt/ibm/db2/V11.1/java/db2jcc.jar com.ibm.db2.jcc.DB2Jcc -version
IBM DB2 JDBC Universal Driver Architecture 3.72.30
java -cp /opt/ibm/db2/V11.1/java/db2jcc4.jar com.ibm.db2.jcc.DB2Jcc -version
IBM Data Server Driver for JDBC and SQLJ 4.23.42
so I tested my Java class using this updated driver: -
java -cp /opt/ibm/db2/V11.1/java/db2jcc.jar:/home/wasadmin DB2connect_ssl2
and it returned: -
Connected to database with type 4 url
000010 CHRISTINE
000020 MICHAEL
000030 SALLY
000050 JOHN
000060 IRVING
000070 EVA
000090 EILEEN
000100 THEODORE
000110 VINCENZO
000120 SEAN
000130 DELORES
000140 HEATHER
000150 BRUCE
000160 ELIZABETH
000170 MASATOSHI
000180 MARILYN
000190 JAMES
000200 DAVID
000210 WILLIAM
000220 JENNIFER
000230 JAMES
000240 SALVATORE
000250 DANIEL
000260 SYBIL
000270 MARIA
000280 ETHEL
000290 JOHN
000300 PHILIP
000310 MAUDE
000320 RAMLAL
000330 WING
000340 JASON
200010 DIAN
200120 GREG
200140 KIM
200170 KIYOSHI
200220 REBA
200240 ROBERT
200280 EILEEN
200310 MICHELLE
200330 HELENA
200340 ROY
000010 CHRISTINE
000020 MICHAEL
000030 SALLY
000050 JOHN
000060 IRVING
000070 EVA
000090 EILEEN
000100 THEODORE
000110 VINCENZO
000120 SEAN
000130 DELORES
000140 HEATHER
000150 BRUCE
000160 ELIZABETH
000170 MASATOSHI
000180 MARILYN
000190 JAMES
000200 DAVID
000210 WILLIAM
000220 JENNIFER
000230 JAMES
000240 SALVATORE
000250 DANIEL
000260 SYBIL
000270 MARIA
000280 ETHEL
000290 JOHN
000300 PHILIP
000310 MAUDE
000320 RAMLAL
000330 WING
000340 JASON
200010 DIAN
200120 GREG
200140 KIM
200170 KIYOSHI
200220 REBA
200240 ROBERT
200280 EILEEN
200310 MICHELLE
200330 HELENA
200340 ROY
The moral of the story - the version of the JDBC driver MAY well be more important than first I had realised.
If you need an updated driver, please go here: -
Final point, here's the Java code that I'm using: -
DB2connect_ssl2.java
import java.lang.*;
import java.sql.*;
import java.io.*;
class DB2connect_ssl2
{
public static void main(String argv[])
{
try
{
Connection con = null;
PreparedStatement pstmt = null;
try
{
Class.forName("com.ibm.db2.jcc.DB2Driver").newInstance();
con = DriverManager.getConnection( "jdbc:db2://odm.uk.ibm.com:60007/SAMPLE" +
":user=db2inst1;password=passw0rd;" +
"sslConnection=true;sslTrustStoreLocation=/home/wasadmin/davehay.jks;sslTrustStorePassword=davehay;" +
"keepAliveTimeout=10;" +
"traceDirectory=/tmp;" +
"traceFile=foobar.trc;" +
"traceFileAppend=false;" +
"traceLevel=-1;" );
System.out.println(" Connected to database with type 4 url") ;
}
catch (Throwable e)
{
System.out.println("Connect failed" + e);
}
try
{
pstmt = con.prepareStatement("select * from DB2INST1.EMP");
}
catch (Throwable e)
{
System.out.println("Statement Prepare failed" + e);
}
try
{
ResultSet s = pstmt.executeQuery();
while (s.next())
{
String v_id = s.getString(1);
String v_stream_id = s.getString(2);
System.out.println( v_id + "" + v_stream_id );
}
s.close();
}
catch (Throwable e)
{
System.out.println("fetch processing failed");
}
con.close();
}
catch (Exception e)
{
System.out.println("Connect failed" + e);
}
}
}
and here's another one: -
JdbcTestDB2.java
import java.sql.Connection ;
import java.sql.DriverManager ;
import java.sql.ResultSet ;
import java.sql.Statement ;
import java.sql.SQLException;
import org.omg.CORBA.VersionSpecHelper;
class JdbcTestDB2
{
public static void main (String args[])
{
try
{
Class.forName("com.ibm.db2.jcc.DB2Driver");
}
catch (ClassNotFoundException e)
{
System.err.println (e) ;
System.exit (-1) ;
}
String hostname = "odm.uk.ibm.com";
int port = 60007;
String dbName = "SAMPLE";
String userName = "db2inst1";
String password = "passw0rd";
String sslConnection = "true";
java.util.Properties properties = new java.util.Properties();
properties.put("user",userName);
properties.put("password", password);
properties.put("sslConnection", sslConnection);
properties.put("sslTrustStoreLocation","/home/wasadmin/davehay.jks");
properties.put("sslTrustStorePassword","davehay");
String url = "jdbc:db2://" + hostname + ":" + port + "/" + dbName;
try
{
Connection connection = DriverManager.getConnection(url,properties);
String query = "select EMPNO,FIRSTNME,LASTNAME from DB2INST1.EMPLOYEE" ;
Statement statement = connection.createStatement () ;
ResultSet rs = statement.executeQuery (query) ;
while ( rs.next () )
System.out.println (rs.getString (1) + "" + rs.getString(2) + "" + rs.getString(3)) ;
connection.close () ;
}
catch (java.sql.SQLException e)
{
System.err.println (e) ;
System.exit (-1) ;
}
}
}