我想使用我的Java应用程序在SSL/TLS握手期间检索已发送的Microsoft SQL Server(2012/2014)的公共(public)服务器证书。
我的环境优先:
为了以编程方式实现,我正在使用自己的信任管理器实现。请在此处查看相关代码的摘录:
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/