我想使用我的 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/