Reputation: 11
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.
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));
}
}
}
}
}
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
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
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
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
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