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

Posted

技术标签:

【中文标题】在握手期间检索公共服务器证书密钥【英文标题】:Retrieve public server certificate key during handshake 【发布时间】:2016-02-24 15:55:29 【问题描述】:

我想在 SSL/TLS 握手期间使用我的 Java 应用程序检索已发送的 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 握手期间,服务器将其公钥证书发送给客户端。”

【问题讨论】:

这应该完全符合您的预期。也许 ssl-handshake 或 tcp-connection 已经失败了? 我不这么认为,因为我可以看到使用 jvm 调试选项发生的握手,如果我手动导出服务器证书并将其放在我的机器上的信任存储中并将该证书用于 ssl连接就可以了。 您的日志记录可能有问题?我测试了代码(使用 System.out.println),调试输出按预期显示 嘿,你看过 Intallcert.java 类了吗? github.com/escline/InstallCert这个类连接到服务器,从客户端获取服务器证书,并将其添加到信任库中......也许你可以稍微修改一下来实现你所需要的 【参考方案1】:

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

问题/问题是 Microsoft 使用的 TDS(表格数据流)协议,它是一种应用层协议,将所有层和连接都包裹在下面。这意味着驱动程序在连接到 Microsoft SQL 服务器或 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=requirenet\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.javas 构造函数JtdsConnection()也必须这样做

在构造函数内部的baseTds.negotiateSSL() 上调用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(),然后在 new Properties() 对象中更改用户、密码、主机等。

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

【讨论】:

以上是关于在握手期间检索公共服务器证书密钥的主要内容,如果未能解决你的问题,请参考以下文章

SSL证书是什么?SSL证书有什么作用?

centos 7 Nginx ssl证书配置

检索 JWT 身份验证的证书/密钥时出现问题。 (节点/Express/C#/IdentityServer)

检索数据时出错

https协议证书部署实战

无法在 Solaris 10 上获取本地颁发者证书