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

sce*_*ler 7 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();
Run Code Online (Sandbox Code Playgroud)

getFactory():

private SSLSocketFactory getFactory() throws IOException
{
    // irrelevant code removed here
    return factory();
}
Run Code Online (Sandbox Code Playgroud)

厂():

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

    return factorySingleton;
}
Run Code Online (Sandbox Code Playgroud)

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};
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

sce*_*ler 4

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

问题/问题是微软使用的TDS(表格数据流)协议,它是一个应用层协议,包装了下面的所有层和连接。这意味着驱动程序在连接到 Microsoft SQL Server 或 Sybase 时必须实现此 TDS 协议(TDS 最初由 Sybase 创建)。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};
}
Run Code Online (Sandbox Code Playgroud)

将被调用。这样,OP 中描述的方式可用于从服务器检索证书。这不是预期的用途,因此需要添加一些丑陋的 getter/setter 和技巧来实际获取证书,其中一种方法是进行以下更改:

对此进行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()));
}
Run Code Online (Sandbox Code Playgroud)

并添加以下字段及其 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;
}
Run Code Online (Sandbox Code Playgroud)

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

if (sslMode != SSL_NO_ENCRYPT)
{
    socket.enableEncryption(ssl);
    setX509Certificate(socket.getX509Certificates());
}
Run Code Online (Sandbox Code Playgroud)

再次拥有与 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;
Run Code Online (Sandbox Code Playgroud)

net\sourceforge\jtds\jdbc\JtdsConnection.javas 构造函数也必须做同样的事情JtdsConnection()

在构造函数内部调用setX509Certificates(baseTds.getX509Certificate())之后调用。该类还必须包含 getter/setter:negotiateSSL()baseTds.negotiateSSL()

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;
Run Code Online (Sandbox Code Playgroud)

最后,人们可以创建自己的实用程序类来利用所有这些添加,如下所示:

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

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

PS.:有人可能想知道为什么所有这些繁琐的更改......例如,由于许可原因无法发布 Microsoft 的 jdbc 驱动程序或不想/无法使用它,这提供了另一种选择。