user1098320
user1098320

Reputation: 11

Disable keystore on JDBC connection

I have an application which connects to a MySQL DB using ConnectorJ without issue. However, I now need to extend the app to perform a mutually-authenticated HTTP request against an additional endpoint.

My trouble is that defining the client keystore for the HTTP request appears to cause the keystore to also be used in the JDBC connection. The MySQL DB is not expecting a certificate to be presented, and the handshake fails as shown below.

SOURCE (simplified)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class DbTester {

    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private static final String JDBC_URL = "jdbc:mysql://XXX.XXX.XXX.XXX/mysql?useSSL=true";
    private static final String JDBC_USERNAME = "xxxxxxxxxx";
    private static final String JDBC_PASSWORD = "xxxxxxxxxx";

    public static void main(final String[] args) throws Exception {

        Class.forName(JDBC_DRIVER);
        Connection connection = DriverManager.getConnection(JDBC_URL, JDBC_USERNAME, JDBC_PASSWORD);

        try (PreparedStatement statement = connection.prepareStatement("select 1 from user")){
            try (ResultSet results = statement.executeQuery()) {
                if (results.next()) {
                    System.out.println("Query Result: " + results.getString(1));
                }
            }
        }
    }
}

EXAMPLE

Sanity test run (successful output):

> java -cp .:mysql-connector-java-5.1.24-bin.jar DbTester
Query Result: 1

Generate arbitrary key:

keytool -genkey -keyalg RSA -keystore myKeystore.jks -storepass password

Re-run with key specified (fail!!):

java -cp .:mysql-connector-java-5.1.24-bin.jar -Djavax.net.ssl.keyStore=myKeystore.jks -Djavax.net.ssl.keyStorePassword=password DbTester

Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 911 milliseconds ago.  The last packet sent successfully to the server was 907 milliseconds ago.
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) 
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
 ...
Caused by: javax.net.ssl.SSLException: Unsupported record version Unknown-0.0
 at sun.security.ssl.InputRecord.readV3Record(InputRecord.java:524)
 at sun.security.ssl.InputRecord.read(InputRecord.java:509)
 at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:927)
 at sun.security.ssl.SSLSocketImpl.waitForClose(SSLSocketImpl.java:1707)
 at sun.security.ssl.HandshakeOutStream.flush(HandshakeOutStream.java:122)
 at sun.security.ssl.Handshaker.sendChangeCipherSpec(Handshaker.java:972)
 at sun.security.ssl.ClientHandshaker.sendChangeCipherAndFinish(ClientHandshaker.java:1087)
 at sun.security.ssl.ClientHandshaker.serverHelloDone(ClientHandshaker.java:1006)
 at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:285)
 at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
 at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
 at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
 at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
 at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
 at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
 at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:90)
... 17 more

VERSIONS

java version "1.7.0_21" Java(TM) SE Runtime Environment (build 1.7.0_21-b11) Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)

mysql server: 5.1.66-log Source distribution

connectorj: mysql-connector-java-5.1.24-bin.jar

EDIT / SOLVED

I've got this working by loading the certificate explicitly for the HTTP connection (and dropping the JVM-level environment params):

        String certPath = System.getProperty("my.cert.path");
        String certPassword = System.getProperty("my.cert.password");

        in = new FileInputStream(new File(certPath));
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(in, certPassword.toCharArray());
        in.close();

        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, certPassword.toCharArray());
        KeyManager[] keymanagers = kmf.getKeyManagers();

        SSLContext context = SSLContext.getInstance("TLS");
        context.init(keymanagers, null, null);
        SSLSocketFactory sslSocketFactory = context.getSocketFactory();

        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(sslSocketFactory);
        // do HTTP connection OPS :)

Upvotes: 0

Views: 3960

Answers (2)

user1098320
user1098320

Reputation: 11

I've got this working by loading the certificate explicitly for the HTTP connection (and dropping the JVM-level environment params):

        String certPath = System.getProperty("my.cert.path");
        String certPassword = System.getProperty("my.cert.password");

        in = new FileInputStream(new File(certPath));
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(in, certPassword.toCharArray());
        in.close();

        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, certPassword.toCharArray());
        KeyManager[] keymanagers = kmf.getKeyManagers();

        SSLContext context = SSLContext.getInstance("TLS");
        context.init(keymanagers, null, null);
        SSLSocketFactory sslSocketFactory = context.getSocketFactory();

        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        // do HTTP connection OPS :)

Upvotes: 1

user207421
user207421

Reputation: 311018

The MySQL DB is not expecting a certificate to be presented

But it asked for one! The client wouldn't have presented a certificate if the server hadn't sent a CertificateRequest message. Either your client isn't acceptable to MySQL, which can be fixed by configuration at the MySQL end, or MySQL shouldn't be asking for client certificates, which can also be fixed at the MySQL end.

Upvotes: 0

Related Questions