简体   繁体   中英

Forcing SSL Client Socket to Send Certificate

I am trying to write a client (a middleware in fact, which is a client to an entity, but also acts as a server to others). In its client capacity it is supposed to talk to another server (VMware's VirtualCenter), and ask it to do stuff on its behalf.

To give you more context, VirtualCenter allows an application to register as an extension. Said application could register its certificate at the time of registration ( setCertificate ). Afterwards, the application can login to VirtualCenter using its certificate ( loginExtensionByCertificate() method) , and thereby not needing to store username and passwords. However, for this to work, the client (my app) must send a certificate as part of its SSL connection, even though the server (VirtualCenter) is not asking for it particularly.

I am writing my app with Java. Created my own key manager, hooked it up to my keystore and specified the alias to use. Then initialized my ssl context to use that key manager. In the created sockets, I do see their SSLContext has my key manager in them. However, I do not see that key manager ever being called to get the certificate. For some reason, the socket does not feel it needs to send a cert.

I understand that the server may ask the client to present its cert. In this case, it does not happen. What I am wondering whether there is a way to force the created socket to present a cert regardless of whether the server asks for it.

Client certificates can only be sent if the server requests it. See TLS Spec. - Client Certificate message (RFC 4346, Section 7.4.6) :

This is the first message the client can send after receiving a server hello done message. This message is only sent if the server requests a certificate.

You can't force the client to send a client certificate is the server hasn't requested one.

EDIT: Of course, you'll also need to make sure your keymanager/keystore is set up properly. You might be facing problems similar to those described in why doesn't java send the client certificate during SSL handshake?

However, for this to work, the client (my app) must send a certificate as part of its SSL connection, even though the server (VirtualCenter) is not asking for it particularly

For that to work, the server must ask for it. That's how the SSL protocol is defined. If you could arrange for your client to send it unsolicited, which you can't, the server would be bound to drop the connection.

The solution to this is at the server end. Are you 100% sure it isn't asking? Maybe it is asking but your client isn't sending? for example, because the certificate isn't signed by a CA the server trusts.

When vCenter is installed it uses a reverse proxy on port 443 that forwards requests over HTTP to the actual service. The proxy never asks for the client certificate neither would it be able to forward it.

Try to find the actual port that the service is running on and connect to that.

The VMware VC will always ask for the SSL certificate of the client, as this is how many authenticated extensions log into VC. So, you're on the right track.

I suspect you're just having problems with SSL/Java interactions, and its not specifically a VMware issue (though its always hard to tell with SSL ...).

I think you want to follow the suggestions over here: Java client certificates over HTTPS/SSL

Finally, solved the problem. It appears VMware takes a very different path. The question was misconstrued.

First, I traced the connection using WireShark, and at no point the server asks (need or want) a certificate. However, VirtualCenter (VC) does provide a way to register a certificate as part of a connection and then use that certificate authenticate. For this to work, they provide a way to tunnel connections.

The sequence I used is:

  • First specify the URL to connect to as https://: (note the protocol is secure, but the port is not).

  • The initial socket will be opened to the clear text port. But having https as the protocol will force calling my own SSLSocketFactory. In my socket factory, I get a call to my public Socket createSocket(Socket s, String host, int port, boolean autoClose) method, which allows me to convert my socket to SSLSocket to my taste.

My code essentially looks like:

@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;
}

In the above block, origSslSockFactory is the SSLSocketFactory that got replaced by your implementation. In my case it was an instance of sun.security.ssl.SSLSocketFactoryImpl.

After that procedure, calling ExtensionManager.setExtensionCertificate() succeeds. And subsequently, all calls to SessionManager.loginExtensionByCertificate() also succeeds.

I wanted to post this as there seem to be a lot of questions about this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM