[
https://issues.apache.org/jira/browse/HTTPCLIENT-1585?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14239151#comment-14239151
]
Oleg Kalnichevski commented on HTTPCLIENT-1585:
-----------------------------------------------
I fixed the test case and raised a PR at Github. The issue is essentially
caused by a combination of several factors
(1) What looks to me like a mess with key / trust material in the test case.
(2) Quirkiness of the JSSE APIs. While SSLContext#init can accept multiple
KeyManager instances the default JSSE provider actually only makes use of the
very first one while silently ignoring the rest.
(3) SSLContextBuilder using plain HashSet to keep track of KeyManager
instances, which results in unstable ordering when those KeyManager instances
are passed to SSLContext#init causing SSLContext to pick up a different
instance of KeyManager.
Oleg
> Client certificate not submitted half the time
> ----------------------------------------------
>
> Key: HTTPCLIENT-1585
> URL: https://issues.apache.org/jira/browse/HTTPCLIENT-1585
> Project: HttpComponents HttpClient
> Issue Type: Bug
> Components: HttpClient
> Affects Versions: 4.3.5, 5.0
> Environment: Fedora 20 on a Thinkpad T540p, Sun JDK 1.6,1.7,1.8
> Reporter: Jim Scarborough
> Labels: clientcertificate
> Attachments: sbs
>
>
> h1. 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.
> h1. Stack trace
> {code:none}
> 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)
> {code}
> h1. Steps to reproduce
> h2. 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}}
>
> h2. 2. Set up Jetty, configure it to require a client cert
>
> {{vim jetty-ssl.xml}}
>
> {code: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>
> {code}
> {{vim jetty-https.xml}}
>
> {code:xml}
> <Set name="port"><Property name="https.port" default="8443" /></Set>
> {code}
> {{vim start.ini}}
>
> {code:none}
> jetty.dump.stop=false
> etc/jetty-ssl.xml
> etc/jetty-https.xml
> {code}
> h2. 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
> h2. 4. Run this code against it:
> {code:title=HttpsClientCertificateTest.java|borderStyle=solid}
> 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;
> }
> }
> {code}
> h1. 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
> h2. Expected results
> No exceptions, test passes
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]