我想使用我的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();
    

    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 Server接收不同的证书,并试图验证它们,请致电我的自定义信任管理器。此时,我将拥有证书(X509Certificate []链)。但是没有调用我的信任管理器,或者至少没有调用两个checker方法内部的断点。

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

    “在SSL握手期间,服务器将其公共(public) key 证书发送给客户端。”

    最佳答案

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

    问题/问题是Microsoft使用的TDS(表格数据流)协议(protocol),它是一个应用程序层协议(protocol),其中包装了其下的所有层和连接。这意味着驱动程序在连接到Microsoft SQL Server或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()必须做同样的事情

    在构造函数内部的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 .:一个人可能想知道为什么所有这些繁琐的更改……例如,由于许可原因,一个人无法交付Microsoft的jdbc驱动程序或不希望/不能使用它,这提供了一种选择。

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

  • 10-10 02:01