强制SSL客户端套接字发送证书

5

我正在尝试编写一个客户端(实际上是一个中间件,它是一个对实体的客户端,但也作为其他实体的服务器)。在其客户端容量中,它应该与另一个服务器(VMware的VirtualCenter)通信,并要求其代表其执行操作。

为了给您更多的背景,VirtualCenter允许应用程序注册为扩展。该应用程序可以在注册时注册其证书(setCertificate)。之后,应用程序可以使用其证书登录VirtualCenter(loginExtensionByCertificate()方法),从而无需存储用户名和密码。但是,为了使此功能正常工作,客户端(我的应用程序)必须在其SSL连接的一部分中发送证书,即使服务器(VirtualCenter)并没有特别要求。

我使用Java编写我的应用程序。创建了自己的密钥管理器,将其连接到密钥库并指定要使用的别名。然后初始化我的ssl上下文以使用该密钥管理器。在创建的套接字中,我确实看到它们的SSLContext中有我的密钥管理器。但是,我没有看到密钥管理器被调用以获取证书的情况。由于某种原因,套接字似乎不需要发送证书。

我知道服务器可能要求客户端呈现其证书。在这种情况下,它并没有发生。我想知道是否有一种方法可以强制创建的套接字无论服务器是否要求都呈现证书。


我建议使用-Djavax.net.debug=ssl或至少-Djavax.net.debug=ssl:handshake来调试握手过程,以查看是否收到了CertificateRequest并检查其中包含的certificate_authorities列表。 - Bruno
5个回答

5

只有在服务器请求时,才能发送客户端证书。请参见TLS规范-客户端证书消息(RFC 4346,第7.4.6节)

这是客户端在接收到服务器hello done消息后可以发送的第一条消息。仅当服务器请求证书时才会发送此消息。

如果服务器没有请求,您无法强制客户端发送客户端证书。

编辑:当然,您还需要确保您的密钥管理器/密钥库已正确设置。您可能会遇到类似于为什么Java在SSL握手期间不发送客户端证书?中描述的问题。


3
然而,要使此操作成功,客户端(我的应用程序)必须在其SSL连接中发送证书,即使服务器(VirtualCenter)并没有特别要求。
为了使其正常工作,服务器必须要求客户端发送证书。这就是SSL协议的定义。如果您可以让客户端主动发送它,但实际上不行,服务器将被迫断开连接。
解决方案在服务器端。您是否100%确定服务器没有要求证书?也许它正在请求,但您的客户端没有发送证书,例如,因为证书未经CA签名,服务器不信任。

你确定要“断开连接”吗?据我所知,IIS可以设置为忽略客户端证书。 - One-One
@desaivv RFC 4346中的“仅在服务器请求证书时才发送此消息”似乎非常清楚;也许它没有义务断开连接,但是JSSE肯定不会发送证书,除非被请求。我对IIS一无所知。 - user207421
1
第7.4节还提到:“握手协议消息应按照下列顺序发送;以意外的顺序发送握手消息会导致致命错误。” 我认为发送一个未请求的 ClientCertificate 消息是“意外的顺序”(因此会中断连接)。@desaivv,你所说的“可以设置IIS忽略客户端证书”是什么意思?(有没有文档/指导?)这里的“忽略”不是指不对其进行身份验证/授权来访问应用程序吗? - Bruno
谢谢提示。我会深入挖掘。至于“不特别要求”的俏皮话:我的意思是,它并不需要它。也就是说,如果它没有出现,它不会断开连接。我认为这就是Java的SSLSocket.setWantClientAuth()和SSLSocket.setNeedClientAuth()之间的区别。我认为VirtualCenter将标志设置为想要,但不需要。我会深入挖掘并报告结果。 - Virtually Real
@VirtuallyReal 的确是这样的区别。在两种情况下,服务器都会通过 CertificateRequest 消息请求证书:在 need 情况下,如果客户端没有发送它,服务器将中止连接;在 want 情况下,如果客户端没有发送它,服务器将继续进行,但如果服务器要求对等证书,则会抛出 PeerUnverifiedException。在同样的情况下,IIS 也会做类似的事情,不要问我具体是什么。所有这些都不允许客户端发送未经请求的证书。 - user207421

0

终于解决了问题。看来VMware采取了非常不同的路径。问题被误解了。

首先,我使用WireShark跟踪连接,在任何时候服务器都没有要求(需要或想要)证书。然而,VirtualCenter(VC)提供了一种注册证书作为连接的一部分,然后使用该证书进行身份验证的方法。为了使其工作,他们提供了一种隧道连接的方式。

我使用的顺序是:

  • 首先指定要连接的URL为https://:(注意协议是安全的,但端口不是)。

  • 初始套接字将打开到明文端口。但是,由于协议是https,将强制调用我的自己的SSLSocketFactory。在我的套接字工厂中,我会收到对我的public Socket createSocket(Socket s, String host, int port, boolean autoClose)方法的调用,这允许我按照自己的喜好将我的套接字转换为SSLSocket。

我的代码基本上是这样的:

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
    throws IOException {
    s.setKeepAlive(true);
    String connectReq = "CONNECT /sdkTunnel HTTP/1.1\r\n\r\n";
    OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
    writer.write(connectReq, 0, connectReq.length());
    writer.flush();
    // In this initial response, all that matters is the http code. If
    // it is 200, the rest can be ignored.
    READ_AND_VERIFY_INITIAL_RESPONSE();
    SSLSocket sock = (SSLSocket) origSslSockFactory
        .createSocket(s, s.getInetAddress().getHostName(), s.getPort(), true);
    // Depending on whether the caller actually does the handshake
    // or not, you may need to call handshake here. In my case, I
    // did not need to, since HTTPClient I am using calls the
    // handshake. Axis does not call, so you may have to in that
    // case.

    // sock.startHandshake();
    return sock;
}

在上面的代码块中,origSslSockFactory是被你的实现替换掉的SSLSocketFactory。在我的情况下,它是sun.security.ssl.SSLSocketFactoryImpl的一个实例。
在这个过程之后,调用ExtensionManager.setExtensionCertificate()成功。随后,所有对SessionManager.loginExtensionByCertificate()的调用也都成功了。
我想发布这篇文章,因为似乎有很多关于这个问题的疑问。

0

VMware VC(虚拟机管理器)将始终要求客户端的SSL证书,因为这是许多经过身份验证的扩展程序登录VC的方式。所以,你正在正确的轨道上。

我怀疑您只是在处理SSL / Java交互方面遇到了问题,而不是特定的VMware问题(尽管很难确定SSL ...)。

我认为您想要遵循此处的建议:通过HTTPS / SSL使用Java客户端证书


0

当安装vCenter时,它会在端口443上使用反向代理将请求转发到实际服务的HTTP。代理服务器从不要求客户端证书,也无法转发它。

尝试查找服务实际运行的端口并连接到该端口。


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