握手期间检索公共服务器证书密钥

时间:2016-02-24 15:55:29

标签: java sql-server ssl

我想使用我的Java应用程序在SSL / TLS握手期间检索Microsoft SQL Server(2012/2014)的已发送公共服务器证书。

我的环境首先:

  • MS SQL设置为使用强制加密
  • 仅接受SSL / TLS连接
  • 包括自签名CA和由该CA颁发的证书
  • 颁发的证书由MS SQL服务器
  • 使用

要以编程方式实现 ,我正在使用自己的信任管理器实现。请在此处查看相关代码的摘录:

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

getFactory():

private SSLSocketFactory getFactory() throws IOException
{
    // irrelevant code removed here
    return 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};
}

我原以为对startHandshake()的调用会在某些时候使我的应用程序从我的SQL服务器接收不同的证书,并试图验证它们是否调用我的自定义信任管理器。此时我将获得证书(X509Certificate []链)。但是我的信任管理器没有被调用,或者至少两个检查器方法中的断点都没有被调用。

这是我用于参考的MS文档之一:https://msdn.microsoft.com/en-us/library/bb879919(v=sql.110).aspx#Anchor_1

“在SSL握手期间,服务器将其公钥证书发送给客户端。” < ---正是我想要/需要的。

1 个答案:

答案 0 :(得分:2)

经过一周的搜索,我发现了问题。在这里可以看到什么不起作用/只是一种解决方法:https://superuser.com/questions/1042525/retrieve-server-certificate-from-sql-server-2012-to-trust

问题/问题是Microsoft使用的TDS(表格数据流)协议,它是一个包装下面所有层和连接的应用层协议。这意味着驱动程序在连接到Microsoft SQL Server或Sybase(TDS最初是由Sybase创建)时必须实现此TDS协议。 FreeTDS就是这样一种实现,对于Java来说,有jTDS,遗憾的是它几乎已经死了。尽管仍然有一些修复已经完成,但没有包括在内并作为新的jTDS版本发布。可以在这里找到jTDS:https://sourceforge.net/projects/jtds/files/但是对于Java 1.8,数据类型发生了变化,导致jTDS向MSSQL发送256字节的无意义,从而无法实现SSL / TLS。这已在r1286(https://sourceforge.net/p/jtds/code/commit_browser

中修复

应用这些更改并至少使用连接字符串属性SSL=require 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};
}

将被调用。有了这个,OP中描述的方法可用于从服务器检索证书。这不是预期的用法,所以需要添加一些丑陋的getter / setter和trickery来实际获得证书,这样的方法有以下变化:

net\sourceforge\jtds\jdbc\SharedSocket.java中将enableEncryption()更改为:

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()));
}

并使用其getter / setter添加以下字段:

private javax.security.cert.X509Certificate[] x509Certificates;

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

public javax.security.cert.X509Certificate[] getX509Certificates()
{
  return x509Certificates;
}

net\sourceforge\jtds\jdbc\TdsCore.java更改negotiateSSL(),以便将其包括在内:

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

再次使用getter / setter完全相同的字段:

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;

net\sourceforge\jtds\jdbc\JtdsConnection.java的构造函数JtdsConnection()

必须这样做

在构造函数内setX509Certificates(baseTds.getX509Certificate())上调用negotiateSSL()后调用baseTds.negotiateSSL()。该类还必须包含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;

最后,可以创建自己的实用程序类来使用所有这些添加:

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

对于属性(它们不是您通常为jdbc找到的所有标准属性),请使用提供的DefaultProperties.addDefaultProperties(),然后在new Properties()对象中更改用户,密码,主机等。

PS:有人可能想知道为什么所有这些繁琐的变化......例如,由于许可原因,无法发送微软jdbc驱动程序或者不想/不能使用它,这提供了替代方案。