Thamindu DJ
Thamindu DJ

Reputation: 1969

Java MTLS implementation doesn't pick any certificate from keystore when using with a connection pool

I'm trying to implement a MTLS communication for a Java client application with a Nginx server. Both the server and client are configured with self generated key pairs. Server public certificate is added to the client trustore and the client's public certificate is provided to the nginx server through the ssl_client_certificate config. Below is the server config at nginx.

server {
    listen                  443 ssl;
    server_name             myserver.io;
    ssl_certificate         certs/server.crt;
    ssl_certificate_key     certs/server.key;
    ssl_protocols           TLSv1.2 TLSv1.3;
    ssl_ciphers             HIGH:!aNULL:!MD5;
    ssl_client_certificate  certs/client.cer;
    ssl_verify_client       optional;
    location / {
    if ($ssl_client_verify != SUCCESS) {
        return 403;
    }
    }
}

The java client uses Apache httpcore, httpclient and httpasyncclient dependencies. Below is a cleaned code snippet I'm using to obtain the client connection.

public static CloseableHttpAsyncClient getClient() throws Exception {

    // Create pooling connection.
    ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor();
    PoolingNHttpClientConnectionManager connectionManager = new PoolingNHttpClientConnectionManager(ioReactor);
    connectionManager.setMaxTotal(20);
    connectionManager.setDefaultMaxPerRoute(20);

    // Create request configuration.
    RequestConfig config = RequestConfig.custom()
        .setConnectTimeout(300).setConnectionRequestTimeout(300)
        .setSocketTimeout(300).setRedirectsEnabled(false)
        .setRelativeRedirectsAllowed(false)
        .build();

    // Create client builder.
    HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClients.custom().setDefaultRequestConfig(config);

    // Construct and set SSL context. 
    // Here keystore and truststore are "java.security.KeyStore" objects.
    SSLContext sslContext = SSLContexts.custom()
            .loadKeyMaterial(keystore, "mypass".toCharArray(), (aliases, socket) - > "mtlsserver")
            .loadTrustMaterial(truststore, null)
            .build();

    httpClientBuilder.setSSLContext(sslContext);
    httpClientBuilder.setSSLHostnameVerifier(new DefaultHostnameVerifier());

    httpClientBuilder.setConnectionManager(connectionManager);
    client = httpClientBuilder.build();
    client.start();
    return client;
}

When running this code, server returns a 403 response indicating not establishing a mtls connection. However the mtls connection works fine when above httpClientBuilder.setConnectionManager(connectionManager); line is removed.

During the ssl handshake, it seems that the client doesn't send any certificates to the server when using it with a connection pool (by setting the connection manager). It sends the correct certificate from the keystore when using a single connection (without the pool).

I'm trying to understand what's happening here and how to resolve this. Any help would be appreciated.

Upvotes: 0

Views: 365

Answers (1)

Thamindu DJ
Thamindu DJ

Reputation: 1969

It looks like MTLS doesn't work in Apache Client 4 library when it's configured with a connection pool. However I was able to get this into working by upgrading my code to work with Apache Core 5 and Client 5 libraries.

A sample code can be extracted from the "Apache HttpClient 5.x migration guide".

Upvotes: 0

Related Questions