java - 在握手期间检索公共(public)服务器证书 key

标签 java sql-server ssl

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

首先是我的环境:

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

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

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

获取工厂():

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 握手期间,服务器将其公钥证书发送给客户端。” <--- 正是我想要/需要的。

最佳答案

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

问题是 Microsoft 使用的 TDS(表格数据流)协议(protocol),它是一个应用层协议(protocol),封装了所有层和下面的连接。这意味着驱动程序在连接到 Microsoft SQL 服务器或 Sybase 时必须实现此 TDS 协议(protocol)(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};
}

将被调用。有了这个,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()));
}

并添加以下字段及其 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()

也必须这样做

negotiateSSL() 被构造函数内部的 baseTds.negotiateSSL() 调用之后调用 setX509Certificates(baseTds.getX509Certificate())。此类还必须包含 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() 然后在 中更改用户、密码、主机等新的 Properties() 对象。

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

关于java - 在握手期间检索公共(public)服务器证书 key ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35606822/

相关文章:

java - 从 Clojure 调用可变参数 Java 函数时出现问题

java - Android:通过套接字发送位图字节数组时遇到问题

sql - 在数据库中存储电子邮件 ID 列表的最佳数据类型是什么?

java.io.FileNotFoundException : . ..keystore.jks(没有这样的文件或目录)在 java.io.FileInputStream.open( native 方法)

java - 创建 ListIterator 时会发生什么?

java - 无法获取完整长度的消息

.net - 使用 Linq-2-Sql 时的 System.Transactions 源警告

mysql - 迭代列的条目并用它们进行计算

SSL Ldap 连接 (ldaps)

java - 如何在本地主机上配置 ssl 证书 dropwizard