.NET和Java之间的SSL Socket并带有客户端身份验证

5
我正在尝试在.NET和Java之间创建SSL Socket服务器/客户端。在这种情况下,我的SSL Socket服务器将在.net中运行,而客户端则在Linux下的Java中运行。我的问题是,在握手期间连接失败,特别是当服务器请求客户端证书时,客户端无法发送回复并且连接失败。
在.NET中,我使用sslStream建立连接,在Java中,我使用标准SSLSocket。以下是一些代码片段,但这是我目前为止的所有内容:
在服务器端(Windows),我在MMC下的个人/证书文件夹中有一个私有证书。我从客户端获得了一个公共证书,它位于受信任的人员/证书下。这两个证书都由同一CA颁发。两个证书的证书链具有多个级别,但对于两个证书来说是相同的。链中的根级别证书也安装在受信任的认证机构/证书文件夹中。
在客户端(Linux)上,我有一个密钥库,其中包含与服务器安装的公共证书匹配的私有证书。我有一个信任库,其中包含服务器的公共证书,与服务器的私有证书匹配。
在服务器端(.net)上,我使用Socket进行异步读取,然后将其包装到SSLStream中,代码片段如下:
NetworkStream ns = new NetworkStream(socket, false);
SslStream ssl = new SslStream(ns, true);
ssl.AuthenticateAsServer(serverCertificate, true, SslProtocols.Default, true);

客户端代码基本上是标准代码:
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
InetAddress addr = InetAddress.getByName(servername);
SSLSocket socket = (SSLSocket) factory.createSocket(addr,port);
socket.setUseClientMode(true);
socket.setNeedClientAuth(true);
socket.setWantClientAuth(true);
socket.startHandshake();
os = new DataOutputStream(socket.getOutputStream());
is = new DataInputStream(socket.getInputStream());
byte[] outBuf = new byte[50];
os.write("SEND SOMETHING".getBytes("UTF-8"));
is.read(outBuf);

在Java中,我已经设置了正确的变量来指向带有密码的信任和密钥存储。

现在,按照标准的SSL握手协议,以下是发生的事情:

  • 客户端Hello
  • 服务器Hello
  • 服务器发送公共证书
  • 客户端使用信任存储中的证书与公共证书匹配
  • 服务器发送证书请求
  • 随着证书请求,服务器发送一个有效CA列表,在此列表中仅发送我的根CA(包括其他众所周知的CA的长列表)。
  • 客户端证书为空。
  • 服务器从客户端接收到一个空证书,因此关闭连接。

就是这样,客户端不会向服务器发送有效证书。我有一些问题:

有人遇到过这样的问题吗?关于服务器(Windows)发送的CA列表,.NET如何确定要向客户端发送什么?是否有一种方法可以修改该列表?我需要在CA列表中发送用于签署我的证书链中的所有机构还是仅需要根机构即可?

我是否在代码的任一侧漏掉了什么?

任何帮助将不胜感激。


问题出在客户端的证书上。该证书未包含完整的链路,因此当服务器发送包含仅根CA的有效CA列表时,客户端无法在存储中找到匹配的证书。我们将带有完整链路的证书重新导入到密钥库中,一切都开始正常工作了。 - Jorge Varona
那么你是如何在JAVA端让它工作的呢?任何信息都将不胜感激。谢谢。 - Mr.Noob
1
@Mr.Noob 很抱歉这么晚才回复。在Java方面,问题是一样的。证书需要具有完整的CA链,因为验证是针对该链进行的。 - Jorge Varona
1个回答

2
以下两个语句在客户端是无用的(尽管它们不应该造成任何损害):
socket.setNeedClientAuth(true);
socket.setWantClientAuth(true);

您看到证书请求消息和客户端证书消息,这表明服务器已正确配置。客户端证书消息中缺少证书的最可能原因是客户端密钥库未正确配置。您可能会对此答案感兴趣,以确保客户端密钥库已正确配置。更具体地说,您需要确保客户端证书的私钥与证书链在同一个别名中导入(并且它是返回到在证书请求消息中广告的CA的链)。
(关于您问题的其余部分,我不确定如何在使用C#的SslStream时修改服务器发送的CA列表。 这个早期的问题似乎表明没有解决方案,尽管自问这个问题以来,新版本的.NET可能已经解决了这个问题。通过查看SslStream API文档和相关类,我找不到任何可以做到这一点的东西,但这并不意味着不存在。)

服务器发送的 CA 列表来自其信任存储区,除非存在覆盖(例如,在 Java 中的 X509TrustManager.getAcceptedIssuers() 覆盖)。 - user207421
@EJP,我已经编辑过了以澄清,抱歉。我从Java方面知道这一点。我不知道的是.Net的SslStream的等效物(这是一个C#服务器)。 - Bruno
它肯定使用Windows信任商店,就像IE一样? - user207421
@EJPпјҢжҲ‘жғіжҳҜиҝҷж ·зҡ„гҖӮд»ҺжңҚеҠЎеҷЁзҡ„и§’еәҰжқҘзңӢпјҢеңЁ SslStream дёӯпјҢRemoteCertificateValidationCallback зӯүеҗҢдәҺ Java зҡ„ X509TM.checkClientTrustedпјҢиҖҢ LocalCertificateSelectionCallback еҲҷзӯүеҗҢдәҺеҜҶй’Ҙз®ЎзҗҶеҷЁгҖӮжҲ‘дёҚзЎ®е®ҡеңЁжӯӨ API дёӯжүҫеҲ°зұ»дјј getAcceptedIssuers() зҡ„зӯүж•Ҳж–№жі•еңЁе“ӘйҮҢгҖӮ - Bruno
感谢您的回复。我已经找到了解决方案。问题出在发布链接的那几行代码上。我在下面发布了完整的答案。 - Jorge Varona

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接