Obtaining a public server certificate key during a handshake

I want to receive the sent Microsoft SQL Server Public Server Certificate (2012/2014) during an SSL / TLS handshake using my Java application.

My environment first:

  • MS SQL is configured to use strong encryption
  • accepts only SSL / TLS connection
  • has, among other things, a self-signed CA and a certificate issued by the specified CA
  • issued certificate is used by MS SQL server

To achieve this programmatically , I use my own trust manager implementation. Please see the relevant code excerpt here:

SSLSocket sslSocket = (SSLSocket) getFactory().createSocket(socket, host, port, true); sslSocket.startHandshake(); 

getFactory ():

 private SSLSocketFactory getFactory() throws IOException { // irrelevant code removed here return factory(); } 

factory ():

 private static SSLSocketFactory factory() throws NoSuchAlgorithmException, KeyManagementException { SSLSocketFactory factorySingleton; SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, getTrustManager(), null); factorySingleton = ctx.getSocketFactory(); return factorySingleton; } 

getTrustManager ():

 private static TrustManager[] getTrustManager() { X509Certificate[] server = null; X509Certificate[] client = null; X509TrustManager tm = new X509TrustManager() { X509Certificate[] server1 = null; X509Certificate[] client1 = null; public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkServerTrusted(X509Certificate[] chain, String x) { server1 = chain; Logger.println("X509 Certificate chain: " + chain); } public void checkClientTrusted(X509Certificate[] chain, String x) { client1 = chain; Logger.println("X509 Certificate chain: " + chain); } }; return new X509TrustManager[]{tm}; } 

I expected that calling startHandshake() at some point would cause my application to receive different certificates from my SQL server and in an attempt to verify that they had called my custom trust manager. At this point, I will have certificates (chain X509Certificate []). But my trust manager is not called, or at least the breakpoints inside both validation methods are not called.

This is one of the MS docs I used for the link: https://msdn.microsoft.com/en-us/library/bb879919(v=sql.110).aspx#Anchor_1

"During SSL confirmation, the server sends the client a public key certificate." <--- exactly what I want / need.

+7
java sql-server ssl
source share
1 answer

After a week of searching, I found a problem. What does not work / only a workaround can be seen here: https://superuser.com/questions/1042525/retrieve-server-certificate-from-sql-server-2012-to-trust

The problem / problem is the TDS (tabular data stream) protocol used by Microsoft, which is an application layer protocol that wraps all the layers and connections below it. This means that the driver must implement this TDS protocol when connecting to a Microsoft SQL or Sybase server (originally TDS was created by Sybase). FreeTDS is such an implementation, and for Java there is jTDS, which, unfortunately, is mostly dead. Despite the fact that they are still fixed, but not included and released as a new version of jTDS. jTDS can be found here: https://sourceforge.net/projects/jtds/files/ , but with Java 1.8 there was a change in the data type, which is why jTDS sent 256 bytes of nonsense to MSSQL, which makes SSL / TLS impossible. This has been fixed in r1286 ( https://sourceforge.net/p/jtds/code/commit_browser )

After applying these changes and using at least the properties of the SSL=require connection string, the user trust manager in net\sourceforge\jtds\ssl\SocketFactories.java :

 private static TrustManager[] trustManagers() { X509TrustManager tm = new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkServerTrusted(X509Certificate[] chain, String x) { // Dummy method } public void checkClientTrusted(X509Certificate[] chain, String x) { // Dummy method } }; return new X509TrustManager[]{tm}; } 

. Moreover, the described method in the OP can be used to extract the certificate from the server. This is not an intended use, so you need to add some ugly getter / setter and trickery to actually get the certificate. One such approach is the following changes:

In net\sourceforge\jtds\jdbc\SharedSocket.java change enableEncryption() to this:

 void enableEncryption(String ssl) throws IOException { Logger.println("Enabling TLS encryption"); SocketFactory sf = SocketFactories.getSocketFactory(ssl, socket); sslSocket = sf.createSocket(getHost(), getPort()); SSLSocket s = (SSLSocket) sslSocket; s.startHandshake(); setX509Certificates(s.getSession().getPeerCertificateChain()); setOut(new DataOutputStream(sslSocket.getOutputStream())); setIn(new DataInputStream(sslSocket.getInputStream())); } 

and add the following field with its recipient / installer:

 private javax.security.cert.X509Certificate[] x509Certificates; private void setX509Certificates(javax.security.cert.X509Certificate[] certs) { x509Certificates = certs; } public javax.security.cert.X509Certificate[] getX509Certificates() { return x509Certificates; } 

In net\sourceforge\jtds\jdbc\TdsCore.java change negotiateSSL() so that it is turned on:

 if (sslMode != SSL_NO_ENCRYPT) { socket.enableEncryption(ssl); setX509Certificate(socket.getX509Certificates()); } 

And again we get the same getter / setter field:

 public javax.security.cert.X509Certificate[] getX509Certificate() { return x509Certificate; } public void setX509Certificate(javax.security.cert.X509Certificate[] x509Certificate) { this.x509Certificate = x509Certificate; } private javax.security.cert.X509Certificate[] x509Certificate; 

The same should be done for the constructor net\sourceforge\jtds\jdbc\JtdsConnection.java JtdsConnection()

to call setX509Certificates(baseTds.getX509Certificate()) after negotiateSSL() been called in baseTds.negotiateSSL() inside the constructor. This class should also contain a getter / setter:

 public javax.security.cert.X509Certificate[] getX509Certificates() { return x509Certificates; } public void setX509Certificates(javax.security.cert.X509Certificate[] x509Certificates) { this.x509Certificates = x509Certificates; } private javax.security.cert.X509Certificate[] x509Certificates; 

Finally, you can create your own utility class to use this entire add-on as follows:

 JtdsConnection jtdsConnection = new JtdsConnection(url, <properties to be inserted>); X509Certificate[] certs = jtdsConnection.getX509Certificates() 

For properties (they are not all standard ones that you usually find for jdbc), use the provided DefaultProperties.addDefaultProperties() and subsequently change the user, password, host, etc. in the new Properties() object.

PS: Is it possible to ask why all these cumbersome changes ... for example, due to licensing reasons, you cannot send the Microsoft jdbc driver or do not want / cannot use it, this also provides an alternative.

+2
source share