You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@hc.apache.org by "Dmitry (JIRA)" <ji...@apache.org> on 2019/03/29 19:56:00 UTC

[jira] [Created] (HTTPCLIENT-1977) HttpClient does not use Keep-Alive if client SSL auth is used

Dmitry created HTTPCLIENT-1977:
----------------------------------

             Summary: HttpClient does not use Keep-Alive if client SSL auth is used
                 Key: HTTPCLIENT-1977
                 URL: https://issues.apache.org/jira/browse/HTTPCLIENT-1977
             Project: HttpComponents HttpClient
          Issue Type: Bug
            Reporter: Dmitry


When client is configured with a certificate for SSL auth, the connection pool stops working properly so each request uses a new connection.

This happens because   when {{AbstractConnPool.getPoolEntryBlocking()}} method is invoked, the state is null so this code
{code:java}
entry = pool.getFree(state);
if (entry == null) {
    break;
}{code}
gets null back. The {{getFree}} method only returns connection with the same state. So when new connection is opened, it kinda gets null state too but later after the request, {{MainClientExec}} does this:
{code:java}
if (userToken != null) {
    connHolder.setState(userToken);
}{code}
and this eventually sets state on the pool entry to the subject of client certificate.
When another request is made - the story repeats, since {{AbstractConnPool.getPoolEntryBlocking()}} uses null state, it cannot find matching connection in the pool (because the only one connection there HAS state now) and a new connection is created. In the end pooled HTTPS connections are never reused.
 
This is the code to reproduce:
{code:java}
import com.alertme.zoo.hubregistration.util.PemUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

public class Test {


    // openssl req -newkey rsa:2048 -nodes -keyout test-client.key -x509 -days 3650 -out test-client.crt -subj '/CN=test-client/'

    private final static String KEY_PEM = "" +
            "-----BEGIN PRIVATE KEY-----\n" +
            "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC+Kzo5Ehd1vrbe\n" +
            "Za+rNIqnIzWAzKr/iWFySQnGQu5SpK1nsZ5JhBK83J4RQxRS6mULrifJgG/AqMlA\n" +
            "0NmCDw0C6p/uQF+VKycntULjnuvwjU04lkTmrWk3twHnvDkd+j2SH+jfzjZePv+l\n" +
            "XCCQLQS5xiMreGjMx4EILUsB00EQoUFbdGg1NgTt7g8L/WuEDC9YybC6FJHOBYP/\n" +
            "02wmW4REYyMuVY3SfZAsOk5YRbEf25c7IAA3+tbaXz9SuRgUK3SKI0ReGBtLyvSj\n" +
            "gyoyOTwT89QOdEsL6KM9TZhjJGpmeXGMHBNP7uEXuXgfB6wKay1ejD6hr+lcgnIC\n" +
            "QqHthASrAgMBAAECggEBAJw6Rwq7oipJE1KBl1+/Omk0s6+sdI6Z/kQ1XKJUOhYK\n" +
            "06pscO1UY1BkrjbgNMIpbfm6iVUw/p34C94DtazzUG0k81535A5X9ULZ1qnI1Ww5\n" +
            "qUbjrJcVv2rWHeqS5xmJiyuQq2+xqVijyMHAfb/0O/2imSINOYuCGq7tBsHpG3rc\n" +
            "k8fpB7Q43+aZRnQNN6F12GwEH/vTSkhbWABSUzLjGxNc1JzArGqhp8Fm2UxuZ+gG\n" +
            "rtfnCQp3AaYrZa1SAxFnA7cfSWiLYv6gv2ejpekbN9Z/iff71in6a0wNOMofbvOv\n" +
            "wLuIuorjHynpc21brJs09Rx4uxJv3kyC8keoWcM22oECgYEA7BwqquZbRAE4EmNM\n" +
            "vWSeXeMnMfQy9u786WoXv3PW7J6ftniKf6f0ZaIdHJv4ilgFf6Xk5ap8e4S2x21W\n" +
            "ciTH/u34O/UTmQ7t5UkGscZzcCldO4vPoU1531md4hXtMcY8W02lRfMtvV9SmlR0\n" +
            "I39eJU0NLb70KgQZ060FUNnL8c0CgYEAzjBQ5IA51KGUOQA81cPrhuUat1/sExgQ\n" +
            "lxsfn5ZW4S74G7fl8W5n6LfX1kZryj11XYEx0Vv3kPuFQ6pjjyv5YiSi7ZgCtD+8\n" +
            "lZcHeadV9V7Mm77x4+pTlJ1ZuMb575OxKOfEX0LmN6qqvW0U5LdSCDa1WWkIqHaa\n" +
            "/MVAM22QOFcCgYEA4PaZZMolTS9IKKT6Wj4DcntbPgppgMQGr7NJOz55GmysyiQh\n" +
            "+i2h/DAxQrANaGsjmhMLfBQrlVjG+k7gHdOTxv8gFKiW6q/B1UP2H+5w0P5oebLl\n" +
            "us/h/gAaIW8418MEgQ4DGhnwi83GG4u6OJRDtJCsrNiTNXFA1mG1fep2mkUCgYEA\n" +
            "k92UdXn7fyBtIr+n4QlC5BdzJGSW8U6Fv0fFUvZG0fCUH5SvQ4gQ3pTRJaqU7JFM\n" +
            "lMTtDB4vGXs3I8KS6X74tkhdy5QDBG7c+E46HyVBANl+VIcIA5HtZJu/V0LixMwe\n" +
            "9Z3YdxSL8wnirjwHCsro+lj5juhDPETqezGeDAObtLsCgYEAqQQvzv/zc/HmzdhI\n" +
            "wvFg8HubQWOhv7LwF16VBLA1I7ojZnTCvIQph9GKmp6NQRY1oHUC62giYDZzCjQ3\n" +
            "2lAYmJ52/vIep2ktoa/tAxJdCLL0ULkpfze8ZJSNN3uSip4KadybAwz7JHuQ9J/5\n" +
            "XKr8FjgTA/zAHLJ9Dft+jqlx9LA=\n" +
            "-----END PRIVATE KEY-----\n";

    private final static String CERT_PEM = "" +
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIDDTCCAfWgAwIBAgIUPKaQbQfY5d36BzcHcJg5MmMaqBQwDQYJKoZIhvcNAQEL\n" +
            "BQAwFjEUMBIGA1UEAwwLdGVzdC1jbGllbnQwHhcNMTkwMzI5MTg0NzI0WhcNMjkw\n" +
            "MzI2MTg0NzI0WjAWMRQwEgYDVQQDDAt0ZXN0LWNsaWVudDCCASIwDQYJKoZIhvcN\n" +
            "AQEBBQADggEPADCCAQoCggEBAL4rOjkSF3W+tt5lr6s0iqcjNYDMqv+JYXJJCcZC\n" +
            "7lKkrWexnkmEErzcnhFDFFLqZQuuJ8mAb8CoyUDQ2YIPDQLqn+5AX5UrJye1QuOe\n" +
            "6/CNTTiWROataTe3Aee8OR36PZIf6N/ONl4+/6VcIJAtBLnGIyt4aMzHgQgtSwHT\n" +
            "QRChQVt0aDU2BO3uDwv9a4QML1jJsLoUkc4Fg//TbCZbhERjIy5VjdJ9kCw6TlhF\n" +
            "sR/blzsgADf61tpfP1K5GBQrdIojRF4YG0vK9KODKjI5PBPz1A50Swvooz1NmGMk\n" +
            "amZ5cYwcE0/u4Re5eB8HrAprLV6MPqGv6VyCcgJCoe2EBKsCAwEAAaNTMFEwHQYD\n" +
            "VR0OBBYEFHVu5hu3Z7NYqZuxroKghvqtlItSMB8GA1UdIwQYMBaAFHVu5hu3Z7NY\n" +
            "qZuxroKghvqtlItSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB\n" +
            "AKPfRWU4W6Zr/HV3hGYIga/arXWpkO/2mW0dN9qdQI3Ok1rXezFhrtxiR5iOaN0f\n" +
            "wYKAUAbfQmfN6Ai4Nl0ZSbuWRe7/Vut/+rwINaARZLoDz3HVeAmi52ZKsikZG9h0\n" +
            "InpDa52Zs/8nnxPo7UiXP05mMTt2psyQGYeolac2FliKchZ7+SvX2FPxSUQ7LkNT\n" +
            "1DGu7jlLkT3plf9rCW0PhIa6vpzyxVVijjqZl86fBU6vaFzJHH6pNoPpQbK4vOmb\n" +
            "2278AHjrDTU6j8IL0uSk8g2klZ8hC9kerblLREp09Haw5l74kwBiypLHV366XbWR\n" +
            "uDXRs4Y1a29pJ41jDhlqllk=\n" +
            "-----END CERTIFICATE-----\n";

    public static void main(String[] args) throws Exception {

        final X509Certificate clientCertificate = PemUtils.loadCertificate(CERT_PEM);
        final PrivateKey clientKey = PemUtils.loadPrivateKey(KEY_PEM);

        final SSLContextBuilder sslContextBuilder = SSLContexts.custom();

        final KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        keystore.load(null, null);

        // When this line is commented out, no client certificate is used and Keep-Alive works as expected
        // and connection gets reused for the second request.
        // However when line is not commented, client SSL cert is used and this breaks connection pool logic
        keystore.setKeyEntry("client", clientKey, new char[]{}, new X509Certificate[]{clientCertificate});

        sslContextBuilder.loadKeyMaterial(keystore, new char[]{});

        final SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                // Do not verify hostname in case we want to use IP in addition to name.
                // After all this is a test only
                sslContextBuilder.build(), new NoopHostnameVerifier());

        final Registry<ConnectionSocketFactory> socketFactoryRegistry =
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("https", sslConnectionSocketFactory)
                        .build();

        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        final CloseableHttpClient client = HttpClients.custom()
                .setConnectionManager(connManager)
                .setDefaultRequestConfig(
                        RequestConfig.custom()
                                .setRedirectsEnabled(false)
                                .build())
                .build();


        // Found the server here https://stackoverflow.com/a/42770043 - it requires client certificate
        // Any "normal" HTTPS server like google.com does not ask for certificate
        // and the issue is only reproducible when client cert is actually used not just added to KeyStore
        final String url = "https://server.cryptomix.com/secure/";

        // Actual responses are irrelevant. The important bit is what tcpdump shows:
        //   sudo tcpdump -pn host server.cryptomix.com
        // there will be two separate connections for each
        // And if client cert is not used, there will be only one

        final CloseableHttpResponse response1 = client.execute(new HttpGet(url));
        System.out.println("response1 = " + response1.getHeaders("Content-Type")[0]);
        EntityUtils.consume(response1.getEntity());

        final CloseableHttpResponse response2 = client.execute(new HttpGet(url));
        System.out.println("response2 = " + response2.getHeaders("Content-Type")[0]);
        EntityUtils.consume(response2.getEntity());

        // The fact two connections were used is also visible in the pool stats
        // as it shows
        //   [leased: 0; pending: 0; available: 2; max: 20]
        // and when keystore.setKeyEntry() is commented out - just 1 available connection
        System.out.println("Pool stats: " + connManager.getTotalStats());
    }
}
{code}



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@hc.apache.org
For additional commands, e-mail: dev-help@hc.apache.org