Question

I want to use mutual SSL authentication between service A and B. I'm currently implementing passing the client certificate from service A in Java. I'm using Apache DefaultHttpClient to execute my requests. I was able to retrieve the client certificate for my service A from an internal credential manager and I keep it as an array of bytes.

DefaultHttpClient client = new DefaultHttpClient();
byte [] certificate = localCertManager.retrieveCert();

I have very little experience in this area and I'd appreciate your help!

I thought maybe it should be somehow passed through arguments in the HTTP client or maybe in the headers.

How do I pass the client certificate with HTTP client?

Was it helpful?

Solution 2

You need to tell an SSLSocketFactory (org.apache.http, not javax) about your keystore, and configure your DefaultHTTPClient to use it for https connections.

An example is here: http://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientCustomSSL.java

OTHER TIPS

The client certificate is sent during the TLS handshake when establishing a connection and can't be sent via HTTP within that connection.

The communication is layered like this:

  • HTTP (application-layer protocol) within
  • TLS (presentation-layer protocol) within
  • TCP (transport-layer protocol) within
  • IP (network-layer protocol)

You need to send the client certificate during the TLS handshake before anything HTTP (methods, headers, URLs, request bodies) is available to be influenced. The server will not accept a client certificate sent later.

I'm recommending switching from DefaultHttpClient (deprecated) to CloseableHttpClient which works more cleanly with try-with-resources.

Apache HttpClient 4.5 makes Mutual TLS reasonably convenient. This answer has been tested with Apache HttpClient 4.5.3.

The essential starting point is using loadKeyMaterial to load your client certicate and it's key (the client keypair) into the SSLContext:

SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(
                MutualHttpsMain.class.getResource(TEST_CLIENT_KEYSTORE_RESOURCE),
                storePassword, keyPassword,
                (aliases, socket) -> aliases.keySet().iterator().next()
        ).build();

And finally building an HTTP client with that socket factory:

CloseableHttpClient httpclient = HttpClients
        .custom().setSSLContext(sslContext).build();

With that client, all your requests can be executed with Mutual TLS authentication implied:

CloseableHttpResponse closeableHttpResponse = httpclient.execute(
        new HttpGet(URI.create("https://mutual-tls.example.com/")));

Here's a full runnable example of mutual TLS with Apache HttpClient:

import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import java.io.Console;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.security.GeneralSecurityException;

public class MutualHttpsMain {
    private static final String TEST_URL = "https://mutual-tls.example.com/";
    private static final String TEST_CLIENT_KEYSTORE_RESOURCE = "/mutual-tls-keystore.p12";

    public static void main(String[] args) throws GeneralSecurityException, IOException {
        Console console = System.console();
        char[] storePassword = console.readPassword("Key+Keystore password: ");
        char[] keyPassword = storePassword;
        SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(
                MutualHttpsMain.class.getResource(TEST_CLIENT_KEYSTORE_RESOURCE),
                storePassword, keyPassword,
                (aliases, socket) -> aliases.keySet().iterator().next()
        ).build();
        try (CloseableHttpClient httpclient = HttpClients
                .custom().setSSLContext(sslContext).build();
             CloseableHttpResponse closeableHttpResponse = httpclient.execute(
                    new HttpGet(URI.create(TEST_URL)))) {
            console.writer().println(closeableHttpResponse.getStatusLine());
            HttpEntity entity = closeableHttpResponse.getEntity();
            try (InputStream content = entity.getContent();
                 ReadableByteChannel src = Channels.newChannel(content);
                 WritableByteChannel dest = Channels.newChannel(System.out)) {
                ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
                while (src.read(buffer) != -1) {
                    buffer.flip();
                    dest.write(buffer);
                    buffer.compact();
                }
                buffer.flip();
                while (buffer.hasRemaining())
                    dest.write(buffer);
            }
        }
    }
}

It's generally better to use Gradle or Maven to run something like this, but in the interest of keeping this Yak shave as minimal as possible I'm providing baseline JDK instructions for building and running this.

Download JARs from the following pages:

Save the full example above as MutualHttpsMain.java.

Copy your PKCS#12 to mutual-tls-keystore.p12 in the same directory.

Compile it as follows (on macOS/Linux/*nix-likes):

javac MutualHttpsMain.java -cp httpclient-4.5.3.jar:httpcore-4.4.8.jar

Or on Windows:

javac MutualHttpsMain.java -cp httpclient-4.5.3.jar;httpcore-4.4.8.jar

Run as follows (on macOS/Linux/*nix-likes):

java -cp httpclient-4.5.3.jar:commons-codec-1.10.jar:commons-logging-1.2.jar:httpcore-4.4.8.jar:. MutualHttpsMain

Run as follows (on Windows):

java -cp httpclient-4.5.3.jar;commons-codec-1.10.jar;commons-logging-1.2.jar;httpcore-4.4.8.jar;. MutualHttpsMain
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top