A self signed certificate is one that is not signed by a Certificate Authority(CA). JAVA stores the certificates of most of the CA (here jre/lib/security/cacerts) and so if you want to connect to an https site with certificate signed by a CA there is no special code for it. (the same code for http call would work)
So basically
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(https_url);
httpclient.execute(httpGet)
But if we have a self signed certificate then we would need to configure the client and make it work. Otherwise we would see
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
javax.net.ssl.SSLPeerUnverifiedException: Host name 'localhost' does not match the certificate subject provided by the peer ...
How to configure http client for this case?
httpclient 4.x
Approach 1
Configure the TrustStrore through debug options
-Djavax.net.ssl.trustStore=/Users/amodpandey/.keystore
Java is able to read the truststore even without passing the password (password of the truststore)
But even after this you might face
javax.net.ssl.SSLPeerUnverifiedException: Host name 'localhost' does not match the certificate subject provided by the peer ...
CloseableHttpClient httpClient =
HttpClientBuilder.create()
.setSSLHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
})
.build();
And it works..
Approach 2
Put it all in the code
CloseableHttpClient httpClient =
HttpClientBuilder.create()
.setSslcontext(SSLContexts.custom().loadTrustMaterial(new File(Thread.currentThread().getContextClassLoader().getResource("keystore").getFile())).build())
.setSSLHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
})
.build();
Approach 3
While using a connection manager
The setSslcontext and setSSLHostnameVerifier are ignore when using
.setConnectionManager(connectionManager)
So connection manager should be configured
Files.copy(ClassLoader.getSystemResourceAsStream("keystore"), Paths.get(URI.create("file:/tmp/keystore")),
StandardCopyOption.REPLACE_EXISTING);
Registry<ConnectionSocketFactory> socketFactoryRegistry =
RegistryBuilder
.<ConnectionSocketFactory>create()
.register(
"https",
new SSLConnectionSocketFactory(SSLContextBuilder.create()
.loadTrustMaterial(new File("/tmp/keystore")).build(),
new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})).register("http", PlainConnectionSocketFactory.INSTANCE).build();
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager(socketFactoryRegistry);
CloseableHttpClient httpClient =
HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.build();
Note
-Djavax.net.debug=all debug option is very helpful to see the certificates being used
The SSLContext uses File and if we plan to package the certificate with the code in a Jar then we would need to create a file to pass it to the File object (it does not for files inside the jar)
Files.copy(ClassLoader.getSystemResourceAsStream("keystore"),Paths.get(URI.create("file:/tmp/utskeystore")),StandardCopyOption.REPLACE_EXISTING)
Related
I am busy with a project where I have to do a GET on an exposed rest service using specific certificates. I am using the apache camel framework with the https4 component. I created a keystore and tested it using soapUI and it connected successfully, but I am however unable to connect through my project.
I used the following page as reference: http://camel.apache.org/http4.html
I set up the SSL for the HTTP Client through the following configuration:
<spring:sslContextParameters id="sslContextParameters">
<spring:keyManagers keyPassword="xxxx">
<spring:keyStore resource="classpath:certificates/keystore.jks" password="xxxx"/>
</spring:keyManagers>
</spring:sslContextParameters>
<setHeader headerName="CamelHttpMethod">
<simple>GET</simple>
</setHeader>
My endpoint is configured as:
<to uri="https4://endpointUrl:9007/v1/{id}?sslContextParametersRef=sslContextParameters"/>
The stacktrace I am receiving:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1904)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:279)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:273)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1446)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:209)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:901)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:837)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1023)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1332)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1359)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1343)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:394)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
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:55)
at org.apache.camel.component.http4.HttpProducer.executeMethod(HttpProducer.java:301)
at org.apache.camel.component.http4.HttpProducer.process(HttpProducer.java:173)
at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:145)
at org.apache.camel.processor.interceptor.TraceInterceptor.process(TraceInterceptor.java:163)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:468)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:197)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:121)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:83)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:197)
at org.apache.camel.component.direct.DirectProducer.process(DirectProducer.java:62)
at org.apache.camel.impl.InterceptSendToEndpoint$1.process(InterceptSendToEndpoint.java:164)
at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:145)
at org.apache.camel.processor.interceptor.TraceInterceptor.process(TraceInterceptor.java:163)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:468)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:197)
at org.apache.camel.processor.ChoiceProcessor.process(ChoiceProcessor.java:117)
at org.apache.camel.processor.interceptor.TraceInterceptor.process(TraceInterceptor.java:163)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:468)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:197)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:121)
at org.apache.camel.processor.Pipeline.access$100(Pipeline.java:44)
at org.apache.camel.processor.Pipeline$1.done(Pipeline.java:139)
at org.apache.camel.processor.CamelInternalProcessor$InternalCallback.done(CamelInternalProcessor.java:257)
at org.apache.camel.processor.RedeliveryErrorHandler$1.done(RedeliveryErrorHandler.java:480)
at org.apache.camel.processor.interceptor.TraceInterceptor$1.done(TraceInterceptor.java:180)
at org.apache.camel.processor.SendProcessor$1.done(SendProcessor.java:155)
at org.apache.camel.processor.CamelInternalProcessor$InternalCallback.done(CamelInternalProcessor.java:257)
at org.apache.camel.processor.Pipeline$1.done(Pipeline.java:148)
at org.apache.camel.processor.CamelInternalProcessor$InternalCallback.done(CamelInternalProcessor.java:257)
at org.apache.camel.processor.RedeliveryErrorHandler$1.done(RedeliveryErrorHandler.java:480)
at org.apache.camel.processor.interceptor.TraceInterceptor$1.done(TraceInterceptor.java:180)
at org.apache.camel.processor.SendProcessor$1.done(SendProcessor.java:155)
at org.apache.camel.component.cxf.CxfClientCallback.handleResponse(CxfClientCallback.java:61)
at org.apache.cxf.endpoint.ClientImpl.onMessage(ClientImpl.java:827)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1672)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream$1.run(HTTPConduit.java:1168)
at org.apache.cxf.workqueue.AutomaticWorkQueueImpl$3.run(AutomaticWorkQueueImpl.java:428)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at org.apache.cxf.workqueue.AutomaticWorkQueueImpl$AWQThreadFactory$1.run(AutomaticWorkQueueImpl.java:353)
at java.lang.Thread.run(Thread.java:745)
Any help would be much appreciated !
Just same: I followed documented instructions and got too stuck on "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target". There's a quick fix, but if you want to link the configuration to the client HTTP session at stake, it becomes a complex set-up.
Method 1:
Doc pages, forums, and this other article would tell you that setting JVM launch options "-Djavax.net.ssl.trustStore=myKeystore.jks -Djavax.net.ssl.trustStorePassword=mystorepass" do solve the issue, provided the remote parties' certificates (self signed, or signed by a CA but then with all the full certificate chain) were all fetched as Trusted certificates in the supplied keystore. Fact is, HTTP4 is based on JSSE, and these java launch options do configure the stack JVM-wide.
As an alternative, you can also fetch peers' certificates (complete chains) in the default JVM keystore jre\lib\security\cacerts (initial password: "changeit") and thus not even need JVM options.
If you have a few outgoing client connections and few peer certificates, this is the simplest way.
Method 2:
In our context, with above 100 remote parties, each requiring certificate updates every 2 years in average, that method implies a JVM reboot on an updated keystore about every week. Our highly available gateway is no longer highly available. So I searched a dynamic/per-connexion/programmatic way.
Below is a simplified excerpt of code from a CAMEL Processor that we use to remotely connect as REST or plain-vanilla HTTP client, with or without SSL/TLS, and with or without client-side certificate (i.e. 2-way SSL/TLS versus 1-way SSL/TLS), as well as combine HTTP Basic Auth as required by peers.
For various reasons the now old CAMEL version 2.16.3 is still used in our context. I have not tested yet newer versions. I suspect no changes given the libraries at stake under the Apache CAMEL layer.
I have added in the code below many comments detailling variant API's to the same effect. So you have clues below to further simplify the code or try alternatives with newer HTTP4 versions. As is, the code works with 2.16, as a CAMEL Processor bean within a Spring application context that contains the entire CAMEL route definition in DSL.
In our context we use java code for configuring entirely dynamic SSL/TLS outbound connexions per session. You should have no difficulties freezing part of the configuration that we set below dynamically via java, into the CAMEL XML DSL as suitable to your context.
Maven dependencies at stake:
<properties>
<camel-version>2.16.3</camel-version>
</properties>
...
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel-version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http4</artifactId>
<version>${camel-version}</version>
<scope>provided</scope>
</dependency>
Code extracted from our org.apache.camel.Processor (I have removed many Exception handling and simplified the code below in order to focus on the solution):
// relevant imports (partial)
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.component.http4.HttpClientConfigurer;
import org.apache.camel.component.http4.HttpComponent;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
...
#Override
public void process(Exchange exchange) throws Exception {
// assume here that we have previously fetched all dynamic connexion parameters in set of java Properties. Of course you can use numerous means to inject connection parameters
Properties params= ... ;
// Trick! 'targetURL' is the URI of the http server to call. Its not the same as the Camel endpoint URI (see further "httpUrlToken" placeHolder), on which you configure endpoint options
// Fact is, we prefer to pass just the target URL as parameter and keep full control on building the CAMEL endpoint URI in java
String targetURL= params.getProperty("targetURL"); // URL to call, e.g. "http://remoteHost.com/some/servlet/path". Will override the placeholder URL set on the endpoint.
// default plain HTTP without SSL/TLS:
String endPointURI = "http4://httpUrlToken?throwExceptionOnFailure=false"; // with option to prevent exceptions from being thrown for failed response codes. It allows us to process all the response codes in a response Processor
// Oh yes! we have to manage a map of HttpComponent instances, because the CAMEL doc clearly tells that each instance can only support a single configuration
// and our true connector is multithreading where each request may go to a different (dynamic) destination with different SSL settings,
// so we actually use a Map of HttpComponent instances of size MAX_THREADS and indexed by the thread ID plus ageing and re-use strategies... but this brings us too far.
// So, for a single thread per client instance, you can just do:
HttpComponent httpComponent = exchange.getContext().getComponent("http4", HttpComponent.class);
// overload in case of SSL/TLS
if (targetURL.startsWith("https")) {
try {
endPointURI = "https4://httpUrlToken?throwExceptionOnFailure=false";
httpComponent = exchange.getContext().getComponent("https4", HttpComponent.class); // well: "https4" and "http4" are the same, so you may skip this line! (our true HttpComponent map is common to secured and unsecured client connexions)
// basic SSL context setup as documented elsewhere, should be enough in theory
SSLContext sslctxt = getSSLContext(exchange, params.getProperty("keystoreFilePath"), params.getProperty("keystorePassword"), params.getProperty("authenticationMode")); // cfr helper method below
HttpClientConfigurer httpClientConfig = getEndpointClientConfigurer(sslctxt); // cfr helper method below
httpComponent.setHttpClientConfigurer(httpClientConfig);
// from here, if you skip the rest of the configuration, you'll get the exception "sun.security.provider.certpath.SunCertPathBuilderException:unable to find valid certification path to requested target"
// the SSL context covers certificate validation but not the host name verification process
// we de-activate here at the connection factory level (systematically... you may not want that), and link the later to the HTTP component
HostnameVerifier hnv = new AllowAll();
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslctxt, hnv);
// You may choose to enforce the BasicHttpClientConnectionManager or PoolingHttpClientConnectionManager, cfr CAMEL docs
// In addition, the following linkage of the connection factory through a Registry that captures the 'https' scheme to your factory is required
Registry<ConnectionSocketFactory> lookup = RegistryBuilder.<ConnectionSocketFactory>create().register("https", sslSocketFactory).build();
HttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(lookup);
// Does not work in 2.16, as documented at http://camel.apache.org/http4.html#HTTP4-UsingtheJSSEConfigurationUtility
// ... keystore and key manager setup ...
// SSLContextParameters scp = new SSLContextParameters();
// scp.setKeyManagers(...);
// httpComponent.setSslContextParameters(scp);
// Not as good as using a connection manager on the HTTP component, although same effects in theory
// HttpClientBuilder clientBuilder = HttpClientBuilder.create();
// clientBuilder.set... various parameters...
// httpClientConfig.configureHttpClient(clientBuilder);
// Commented-out alternative method to set BasicAuth with user and password
// HttpConfiguration httpConfiguration = new HttpConfiguration();
// httpConfiguration.setAuthUsername(authUsername);
// ... more settings ...
// httpComponent.setHttpConfiguration(httpConfiguration);
// setClientConnectionManager() is compulsory to prevent "SunCertPathBuilderException: unable to find valid certification path to requested target"
// if instead we bind the connection manager to a clientBuilder, that doesn't work...
httpComponent.setClientConnectionManager(connManager);
} catch (Exception e) { ... ; }
}
// (back to code common to secured and unsecured client sessions)
// additional parameters on the endpoint as needed, cfr API docs
httpComponent.set...(...) ;
// you may want to append these 3 URI options in case of HTTP[S] with Basic Auth
if (... basic Auth needed ...)
endPointURI += "&authUsername="+params.getProperty("user")+"&authPassword="+params.getProperty("password")+"&authenticationPreemptive=true";
// *********** ACTUAL TRANSMISSION ********************
exchange.getIn().setHeader(Exchange.HTTP_URI, targetURL); // needed to overload the "httpUrlToken" placeholder in the endPointURI
// Next, there are many ways to get a CAMEL Producer or ProducerTemplate
// e.g. httpComponent.createEndpoint(endPointURI).createProducer()
// ... in our case we use a template injected from a Spring application context (i.e. <camel:template id="producerTemplate"/>) via constructor arguments on our Processor bean
try {
producerTemplate.send(httpComponent.createEndpoint(endPointURI),exchange);
} catch (Exception e) { ...; }
// you can then process the HTTP response here, or better dedicate the next
// Processor on the CAMEL route to such handlings...
...
}
Supporting helper methods, invoked by above code
private HttpClientConfigurer getEndpointClientConfigurer(final SSLContext sslContext) {
return new HttpClientConfigurer(){
#Override
public void configureHttpClient(HttpClientBuilder clientBuilder) {
// I put a logger trace here to see if/when the ssl context is actually applied, the outcome was ... weird, try it!
clientBuilder.setSSLContext(sslContext);
}
};
}
/**
* Build a SSL context with keystore and other parameters according to authentication mode.
* The keystore may just contain a trusted peer's certificate for 1way cases, and the associated certificate chain up to a trusted root as applicable.
* The keystore shall too contain one single client private key and certificate for 2way modes. We assume here a same password on keystore and private key.
* #param authenticationMode one of "1waySSL" "1wayTLS" "2waySSL" "2wayTLS" each possibly suffixed by "noCHECK" as in "1waySSLnoCHECK"
* #param keystoreFilePath can be null for "noCHECK" modes
* #param keystorePassword would be null if above is null
*/
private SSLContext getSSLContext(Exchange exchange, String keystoreFilePath, String keystorePassword, String authenticationMode) throws GeneralSecurityException, FileNotFoundException, IOException {
SSLContext sslContext = SSLContext.getInstance(authenticationMode.substring(4,7).toUpperCase(),"SunJSSE");
//enforce Trust ALL ? pass a trust manager that does not validate certificate chains
if (authenticationMode.endsWith("noCHECK")) {
TrustManager[] trustAllCerts = new TrustManager[]{ new TrustALLManager()};
sslContext.init(null , trustAllCerts, null);
return sslContext;
}
// we use https, and validate remote cert's by default, henceforth keystore and password become compulsory
if (null == keystoreFilePath || null == keystorePassword)
throw new GeneralSecurityException("Config ERROR: using https://... and implicit default AUTHMODE=1waySSL altogether requires to supply keystore parameters");
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
trustStore.load(new FileInputStream(keystoreFilePath), keystorePassword.toCharArray());
tmf.init(trustStore);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
if (authenticationMode.charAt(0)=='2') { // our authenticationMode starts with 1way.. or 2way...
// 2way... case: set the keystore parameters accordingly
keyStore.load(new FileInputStream(keystoreFilePath), keystorePassword.toCharArray());
kmf.init(keyStore, keystorePassword.toCharArray());
sslContext.init(kmf.getKeyManagers() , tmf.getTrustManagers(), new SecureRandom());
} else { // 1way... case
sslContext.init(null , tmf.getTrustManagers(), new SecureRandom());
}
return sslContext;
}
// Create a trust manager that does not validate certificate chains
private class TrustALLManager implements X509TrustManager {
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
#Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
private static class AllowAll implements HostnameVerifier
{
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
}
}
Hope this helps. I spent many hours trying to get it working (although I know well about SSL/TLS principles, security, X509, etc) ... This code is far from my taste for clean and lean java code. In addition I assumed that you do know how to build a keystore, supply all needed certificate chains, define a CAMEL route, etc. As such, it works with Camel 2.16 within a Spring Application Context, and has no other pretention than providing clues that would save you hours.
One more to the list of the mysterious "peer not authenticated".
I have an apache httpclient using 4.2 lib. I have explicitly set to trust all certificates in the code.
I have a Tomcat server (JRE 1.7U45), serving the requests on Linux. The server has a self signed certificate.
Client side code:
private DefaultHttpClient getHttpsClient() {
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
final SSLSocketFactory sf;
sslContext.init(null, new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs,
String authType) {
}
public void checkServerTrusted(X509Certificate[] certs,
String authType) {
}
} }, new SecureRandom());
sf = new SSLSocketFactory(sslContext,
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme(url.getScheme(), url.getPort(), sf));
ClientConnectionManager cm = new BasicClientConnectionManager(
registry);
return new MyDefaultHttpClient(cm);
} catch (Exception e) {
return new MyDefaultHttpClient();
}
}
This error is only seen intermittently on "Solaris 5.10" (32 bit JRE 1.7.0u45) clients talking to the server.
Sometime, the request on the same box go thru fine, but at other times, this just throws "Peer Not Authenticate"
I have other flavors of OS clients, where the call is going thru just fine.
Would any of have any suggestions/pointers to look into this issue?
More Update:
Ran the ssl debug on the server and we see that intermittently, it throws
http-bio-8443-exec-7, handling exception: javax.net.ssl.SSLHandshakeException: Invalid Padding length: 105
http-bio-8443-exec-7, IOException in getSession(): javax.net.ssl.SSLHandshakeException: Invalid Padding length: 105
This was due the following bug in JRE 1.7 http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8013059
Also, the apache httpclient 4.2 added to the confusion, where it masking the actual exception thrown instead throwing the generic "Peer not authenticated"
In the server.xml of tom-cat, for connector element, add the cipher attribute with a list of non-DH ciphers
E.g.
ciphers="SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA,SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA"
This solved the issue.
Hope this is useful to someone.
Thanks
I'm trying to use mutual authentication (certs on both client and server) but I'm then getting this error:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Then I found this class which I tried to use as a messageSender:
HttpsUrlConnectionMessageSender
But I can't figure out how to setup keystores and truststores which I have configure with javax.net.ssl properties.
If possible I would like to do this setup in spring xml files so it can dynamically configured.
Checkout here.
You just have to generate a certificate and place in JRE library path and no need to use any message sender.
http://www.mkyong.com/webservices/jax-ws/suncertpathbuilderexception-unable-to-find-valid-certification-path-to-requested-target/
Certificate generator https://code.google.com/p/java-use-examples/source/browse/trunk/src/com/aw/ad/util/InstallCert.java
Then if you face this exception java.security.cert.CertificateException
Then just override the verifier
static {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
// HOST_ADDRESS = Endpoint host address
if (hostname.equals(HOST_ADDRESS))
return true;
return false;
}
});
}
I have a Java Key Store where I store certificates for each of my customer's sub-domain. I am planning to use the server alias to differentiate between multiple customers in the key store as suggested here. Play framework 1.2.7 uses Netty's SslHandler to support SSL on the server-side. I tried implementing a custom SslHttpServerContextFactory that uses this solution.
import play.Play;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Properties;
public class CustomSslHttpServerContextFactory {
private static final String PROTOCOL = "SSL";
private static final SSLContext SERVER_CONTEXT;
static {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
SSLContext serverContext = null;
KeyStore ks = null;
try {
final Properties p = Play.configuration;
// Try to load it from the keystore
ks = KeyStore.getInstance(p.getProperty("keystore.algorithm", "JKS"));
// Load the file from the conf
char[] certificatePassword = p.getProperty("keystore.password", "secret").toCharArray();
ks.load(new FileInputStream(Play.getFile(p.getProperty("keystore.file", "conf/certificate.jks"))),
certificatePassword);
// Set up key manager factory to use our key store
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(ks, certificatePassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
tmf.init(ks);
final X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
X509KeyManager km = new X509KeyManagerWrapper(origKm);
// Initialize the SSLContext to work with our key managers.
serverContext = SSLContext.getInstance(PROTOCOL);
serverContext.init(new KeyManager[]{km}, tmf.getTrustManagers(), null);
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e);
}
SERVER_CONTEXT = serverContext;
}
public static SSLContext getServerContext() {
return SERVER_CONTEXT;
}
public static class X509KeyManagerWrapper implements X509KeyManager {
final X509KeyManager origKm;
public X509KeyManagerWrapper(X509KeyManager origKm) {
this.origKm = origKm;
}
public String chooseServerAlias(String keyType,
Principal[] issuers, Socket socket) {
InetAddress remoteAddress = socket.getInetAddress();
//TODO: Implement alias selection based on remoteAddress
return origKm.chooseServerAlias(keyType, issuers, socket);
}
#Override
public String chooseClientAlias(String[] keyType,
Principal[] issuers, Socket socket) {
return origKm.chooseClientAlias(keyType, issuers, socket);
}
#Override
public String[] getClientAliases(String s, Principal[] principals) {
return origKm.getClientAliases(s, principals);
}
#Override
public String[] getServerAliases(String s, Principal[] principals) {
return origKm.getServerAliases(s, principals);
}
#Override
public X509Certificate[] getCertificateChain(String s) {
return origKm.getCertificateChain(s);
}
#Override
public PrivateKey getPrivateKey(String s) {
return origKm.getPrivateKey(s);
}
}
}
But, this approach did not work for some reason. I get this message in my SSL debug log.
X509KeyManager passed to SSLContext.init(): need an X509ExtendedKeyManager for SSLEngine use
This is the SSL trace, which fails with "no cipher suites in common". Now, I switched the wrapper to:
public static class X509KeyManagerWrapper extends X509ExtendedKeyManager
With this change, I got rid of the warning, but I still see the same error as before "no cipher suites in common" and here is the SSL trace. I am not sure why the delegation of key manager won't work.
Some more information that may be useful in this context.
Netty uses javax.net.ssl.SSLEngine to support SSL in NIO server.
As per the recommendation in this bug report, it is intentional that X509ExtendedKeyManager must be used with an SSLEngine. So, the wrapper must extend X509ExtendedKeyManager.
This is hindering me to move further with the custom alias selection logic in X509KeyManagerWrapper. Any clues on what might be happening here? Is there any other way to implement this in Netty/Play? Appreciate any suggestions.
SSLEngine uses the chooseEngineServerAlias method to pick the certificate to use (in server mode) - not the chooseServerAlias method.
The default chooseEngineServerAlias implementation actually returns null, which is what causes the "no cipher suites in common" message - you need a certificate to know which cipher suites can be used (e.g. ECDSA can only be used for authentication if the certificate has an ECC public key, etc.) There are actually some cipher suites which can be used without a certificate, however, these are typically disabled as they are vulnerable to MITM attacks.
Therefore, you should also override chooseEngineServerAlias, and implement your logic to select the certificate based on the IP address there. As Netty only uses SSLEngine, what chooseServerAlias does doesn't matter - it'll never be called.
Java 8 also has support for server-side SNI, which allows you to use several certificates across many hostnames with a single IP address. Most web browsers support SNI - the notable exceptions are IE running on Windows XP and some old versions of Android, however, usage of these is declining. I have created a small example application demonstrating how to use SNI in Netty on GitHub. The core part of how it works is by overriding chooseEngineServerAlias - which should give you enough hints, even if you want to use the one certificate per IP address technique instead of SNI.
(I posted a similar answer to this on the Netty mailing list, where you also asked this question - however, my post seems to have not yet been approved, so I thought I'd answer here too so you can get an answer sooner.)
We have an issue in our environment when using Websphere to attempt to connect to an external system with HttpClient 4.x (current version is 4.2.1). Connecting to the external system is fine with their certificate being installed in Websphere with no additional configuration of HttpClient. However, when they enabled mutual authentication, it no longer works and we get a SSLPeerUnverifiedException exception:
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated,
at com.ibm.jsse2.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:105),
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128),
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:572),
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180),
at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:294),
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:640),
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:479),
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906),
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:1066),
I was provided the following code sample, and I was wondering if there's any way to configure HttpClient to use an explicit alias like this code sample does. I've tried to find good documentation on using SSL mutual authentication with HttpClient 4 and haven't been able to find much.
Here's the code sample:
private HttpURLConnection getConnection(String server, String machine,
String port) throws Exception {
URL u = new URL(server);
HttpsURLConnection connection = (HttpsURLConnection) u.openConnection();
String alias = "CellDefaultSSLSettings";
final HashMap connectionInfo = new HashMap();
connectionInfo.put(JSSEHelper.CONNECTION_INFO_DIRECTION,
JSSEHelper.DIRECTION_OUTBOUND);
connectionInfo.put(JSSEHelper.CONNECTION_INFO_REMOTE_HOST, machine);
connectionInfo.put(JSSEHelper.CONNECTION_INFO_REMOTE_PORT, port);
javax.net.ssl.SSLSocketFactory sslFact = JSSEHelper.getInstance()
.getSSLSocketFactory(alias, connectionInfo, null);
connection.setSSLSocketFactory(sslFact);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST");
return connection;
}
Basically, how do I make HttpClient use "CellDefaultSSLSettings"?
Fundamentally this problem has nothing to do with HttpClient. HttpClient can be configured to establish HTTPS connections using any custom SSLContext or SSLSocketFactory instance. This is basically about how to use JSSE APIs to configure SSLContext in the right way. In your particular case JSSEHelper does all the hard work for you.
// JSSE socket factory
javax.net.ssl.SSLSocketFactory jssesf = JSSEHelper.getInstance().getSSLSocketFactory(alias, connectionInfo, null);
// HC socket factory
SSLSocketFactory hcsf = new SSLSocketFactory(jssesf, SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
This will give a connection socket factory that can be registered with the connection manager.
HttpClient 4.3 also comes with SSLContextBuilder class which can be used to assemble custom SSL configurations using fluid builder API.
https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/conn/ssl/SSLContextBuilder.java
oleg's answer helped me out.
What I did was extend the DefaultHttpClient, and each constructor takes a String argument for the destination URL and calls a method setupScheme:
private void setupScheme(final String url) throws Exception {
Scheme scheme = new Scheme("https", 443, retrieveWebsphereSSLConnectionFactory(url));
getConnectionManager().getSchemeRegistry().register(scheme);
}
The method retrieveWebsphereSSLConnectionFactory essentially combines the code from the sample with the code oleg provided:
private SchemeSocketFactory retrieveWebsphereSSLConnectionFactory(final String url)
throws SSLException, URISyntaxException {
final String alias = "CellDefaultSSLSettings";
final HashMap<String, String> connectionInfo = new HashMap<String, String>();
connectionInfo.put(JSSEHelper.CONNECTION_INFO_DIRECTION, JSSEHelper.DIRECTION_OUTBOUND);
connectionInfo.put(JSSEHelper.CONNECTION_INFO_REMOTE_HOST,
URIUtils.extractHost(new URI(url)).getHostName());
connectionInfo.put(JSSEHelper.CONNECTION_INFO_REMOTE_PORT, "443");
return new SSLSocketFactory(JSSEHelper.getInstance().getSSLSocketFactory(alias, connectionInfo, null),
SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
}