How do I pass the client certificate with HTTP client?

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


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:

  • Apache HttpClient 4.5.3
  • Apache Commons Codec 1.10
  • Apache Commons Logging 1.2
  • Apache HttpCore 4.4.8

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