Uploaded image for project: 'HttpComponents HttpClient'
  1. HttpComponents HttpClient
  2. HTTPCLIENT-1585

SSLContextBuilder does not preserve natural order of Trust / Key managers

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Minor
    • Resolution: Fixed
    • 4.3.6, 4.4 Beta1
    • 4.4 Final
    • HttpClient (classic)
    • Fedora 20 on a Thinkpad T540p, Sun JDK 1.6,1.7,1.8

    Description

      Overview

      The http client submits the client certificate when requested about half the time. This may well be a bug in the underlying SSLSocketImpl or nearby code. I welcome assistance in determining which layer has the bug.

      Stack trace

      javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
      	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
      	at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
      	at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1959)
      	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1077)
      	at sun.security.ssl.SSLSocketImpl.waitForClose(SSLSocketImpl.java:1705)
      	at sun.security.ssl.HandshakeOutStream.flush(HandshakeOutStream.java:122)
      	at sun.security.ssl.Handshaker.sendChangeCipherSpec(Handshaker.java:982)
      	at sun.security.ssl.ClientHandshaker.sendChangeCipherAndFinish(ClientHandshaker.java:1154)
      	at sun.security.ssl.ClientHandshaker.serverHelloDone(ClientHandshaker.java:1066)
      	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:341)
      	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:878)
      	at sun.security.ssl.Handshaker.process_record(Handshaker.java:814)
      	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
      	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
      	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
      	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
      	at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:275)
      	at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:254)
      	at org.apache.http.impl.conn.HttpClientConnectionOperator.connect(HttpClientConnectionOperator.java:123)
      	at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:318)
      	at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:363)
      	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:219)
      	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:195)
      	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:86)
      	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:108)
      	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
      	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
      	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106)
      

      Steps to reproduce

      1. Set up certificates

      create server key/cert pair. use FQDN as First Last name
      keytool -keystore server_keystore -alias server -genkey -keyalg RSA

      export server cert to a curl usable format.
      keytool -exportcert -alias server -keystore server_keystore -rfc -file server.pem

      create client key/cert pair. use email as First Last name
      keytool -keystore client_keystore -alias client -genkey -keyalg RSA

      export client key/cert pair to a firefox usablabe format.
      keytool -importkeystore -srckeystore client_keystore -destkeystore client_keystore.pkcs12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass password -deststorepass password -srcalias client -destalias client -srckeypass password -destkeypass password

      export client key/cert pair to a curl usable format.
      openssl pkcs12 -in client_keystore.pkcs12 -out client.crt.pem -clcerts -nokeys
      openssl pkcs12 -in client_keystore.pkcs12 -out client.key.pem -nocerts -nodes

      2. Set up Jetty, configure it to require a client cert

      vim jetty-ssl.xml

         <Set name="KeyStorePath"><Property name="jetty.base" default="." />/<Property name="jetty.keystore" default="etc/server_keystore"/></Set>
         <Set name="KeyStorePassword"><Property name="jetty.keystore.password" default="password"/></Set>
         <Set name="KeyManagerPassword"><Property name="jetty.keymanager.password" default="password"/></Set>
         <Set name="TrustStorePath"><Property name="jetty.base" default="." />/<Property name="jetty.truststore" default="etc/client_keystore"/></Set>
         <Set name="TrustStorePassword"><Property name="jetty.truststore.password" default="password"/></Set>
         <Set name="NeedClientAuth"><Property name="jetty.ssl.needClientAuth" default="true"/></Set>
      

      vim jetty-https.xml

          <Set name="port"><Property name="https.port" default="8443" /></Set>
      

      vim start.ini

          jetty.dump.stop=false
          etc/jetty-ssl.xml
          etc/jetty-https.xml
      

      3. Verify Jetty works

      Copy the exported certs to your machine.

      Go to https://FQDN:8443 in the browser with and without the cert to make sure it works

      or use curl

      curl -L -v --cacert server.pem -E ./client.crt.pem --key ./client.key.pem https://localhost:8443

      4. Run this code against it:

      HttpsClientCertificateTest.java
      import javax.net.ssl.SSLHandshakeException;
      import javax.net.ssl.SSLParameters;
      import javax.net.ssl.SSLSocket;
      import javax.net.ssl.SSLSocketFactory;
      import java.net.Socket;
      
      import com.google.common.io.Resources;
      import junit.framework.AssertionFailedError;
      import org.junit.Test;
      
      import static org.junit.Assert.*;
      
      public class HttpsClientCertificateTest {
          @Test
          public void testClientCertificate() throws Exception {
      
              int success=0;
              int failure=0;
      
              for (int i=0;i<40;i++) {
                  try {
                      String testKeystorePath = Resources.getResource("test-keystore").toString();
                      String testClientCertPath = Resources.getResource("test-clientstore").toString();
      
                      assertEquals("protected\n",secureContentFor("https://localhost:8443/", testKeystorePath, testClientCertPath));
                      success++;
                  } catch (AssertionFailedError e) {
                      e.printStackTrace();
                      failure++;
                  } catch (SSLHandshakeException e) {
                      e.printStackTrace();
                      failure++;
                  }
              }
              assertEquals("failed " + failure + "/" + (success+failure) + " succeeded " + (success*100/(success+failure)) + "%",0,failure);
          }
      
      
          static String secureContentFor(String url, String clientKeyStore, String clientTrustStore) throws Exception {
              KeyStore trustStore = readKeyStore(clientTrustStore);
              KeyStore keyStore = readKeyStore(clientKeyStore);
      
              // Trust own CA and all self-signed certs
              SSLContext sslcontext = SSLContexts.custom()
                      .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy())
                      .loadKeyMaterial(keyStore, "password".toCharArray())
                      .loadKeyMaterial(trustStore, "password".toCharArray())
                      .useTLS()
                      .build();
      
              // Allow TLSv1 protocol only
              SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                      sslcontext,
                      new String[] { "TLSv1" }, // supported protocols
                      null,  // supported cipher suites
                      SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
      
              CloseableHttpClient httpClient = HttpClients.custom()
                      .setSSLSocketFactory(sslsf)
                      .build();
      
              HttpGet get = new HttpGet(url);
              HttpResponse response = httpClient.execute(get);
              String content = EntityUtils.toString(response.getEntity());
              return content;
          }
      
          static KeyStore readKeyStore(String resourceURL) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
              KeyStore trustStore  = KeyStore.getInstance(KeyStore.getDefaultType());
              FileInputStream instream = new FileInputStream(new URL(resourceURL).getFile());
              try {
                  trustStore.load(instream, "password".toCharArray());
              } finally {
                  instream.close();
              }
              return trustStore;
          }
      
      }
      
      

      Actual results

      Exceptions as noted above, and some ultimate failure note like this:
      java.lang.AssertionError: failed 19/40 succeeded 52%
      Expected :0
      Actual :19

      Expected results

      No exceptions, test passes

      Attachments

        1. sbs
          238 kB
          Jim Scarborough

        Activity

          People

            Unassigned Unassigned
            ke4roh Jim Scarborough
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: