Java SSLException: hostname in certificate didn't match - java

I have been using the following code to connect to one of google's service. This code worked fine on my local machine :
HttpClient client=new DefaultHttpClient();
HttpPost post = new HttpPost("https://www.google.com/accounts/ClientLogin");
post.setEntity(new UrlEncodedFormEntity(myData));
HttpResponse response = client.execute(post);
I put this code in a production environment, which had blocked Google.com. On request, they allowed communication with Google server by allowing me to accessing an IP : 74.125.236.52 - which is one of Google's IPs. I edited my hosts file to add this entry too.
Still I could not access the URL, which I wonder why. So I replaced the above code with :
HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");
Now I get an error like this :
javax.net.ssl.SSLException: hostname in certificate didn't match:
<74.125.236.52> != <www.google.com>
I guess this is because Google has multiple IPs. I cant ask the network admin to allow me access to all those IPs - I may not even get this entire list.
What should I do now ? Is there a workaround at Java level ? Or is it totally in hands of the network guy ?

You can also try to set a HostnameVerifier as described here. This worked for me to avoid this error.
// Do not do this in production!!!
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
DefaultHttpClient client = new DefaultHttpClient();
SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());
// Set verifier
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
// Example send http request
final String url = "https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);

The certificate verification process will always verify the DNS name of the certificate presented by the server, with the hostname of the server in the URL used by the client.
The following code
HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");
will result in the certificate verification process verifying whether the common name of the certificate issued by the server, i.e. www.google.com matches the hostname i.e. 74.125.236.52. Obviously, this is bound to result in failure (you could have verified this by browsing to the URL https://74.125.236.52/accounts/ClientLogin with a browser, and seen the resulting error yourself).
Supposedly, for the sake of security, you are hesitant to write your own TrustManager (and you musn't unless you understand how to write a secure one), you ought to look at establishing DNS records in your datacenter to ensure that all lookups to www.google.com will resolve to 74.125.236.52; this ought to be done either in your local DNS servers or in the hosts file of your OS; you might need to add entries to other domains as well. Needless to say, you will need to ensure that this is consistent with the records returned by your ISP.

I had similar problem. I was using Android's DefaultHttpClient. I have read that HttpsURLConnection can handle this kind of exception. So I created custom HostnameVerifier which uses the verifier from HttpsURLConnection. I also wrapped the implementation to custom HttpClient.
public class CustomHttpClient extends DefaultHttpClient {
public CustomHttpClient() {
super();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier(new CustomHostnameVerifier());
Scheme scheme = (new Scheme("https", socketFactory, 443));
getConnectionManager().getSchemeRegistry().register(scheme);
}
Here is the CustomHostnameVerifier class:
public class CustomHostnameVerifier implements org.apache.http.conn.ssl.X509HostnameVerifier {
#Override
public boolean verify(String host, SSLSession session) {
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify(host, session);
}
#Override
public void verify(String host, SSLSocket ssl) throws IOException {
}
#Override
public void verify(String host, X509Certificate cert) throws SSLException {
}
#Override
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
}
}

A cleaner approach ( only for test environment) in httpcliet4.3.3 is as follows.
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

In httpclient-4.3.3.jar, there is another HttpClient to use:
public static void main (String[] args) throws Exception {
// org.apache.http.client.HttpClient client = new DefaultHttpClient();
org.apache.http.client.HttpClient client = HttpClientBuilder.create().build();
System.out.println("HttpClient = " + client.getClass().toString());
org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/");
org.apache.http.HttpResponse response = client.execute(post);
java.io.InputStream is = response.getEntity().getContent();
java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is));
String line;
while ((line = rd.readLine()) != null) {
System.out.println(line);
}
}
This HttpClientBuilder.create().build() will return org.apache.http.impl.client.InternalHttpClient. It can handle the this hostname in certificate didn't match issue.

Thanks Vineet Reynolds. The link you provided held a lot of user comments - one of which I tried in desperation and it helped. I added this method :
// Do not do this in production!!!
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){
public boolean verify(String string,SSLSession ssls) {
return true;
}
});
This seems fine for me now, though I know this solution is temporary. I am working with the network people to identify why my hosts file is being ignored.

The concern is we should not use ALLOW_ALL_HOSTNAME_VERIFIER.
How about I implement my own hostname verifier?
class MyHostnameVerifier implements org.apache.http.conn.ssl.X509HostnameVerifier
{
#Override
public boolean verify(String host, SSLSession session) {
String sslHost = session.getPeerHost();
System.out.println("Host=" + host);
System.out.println("SSL Host=" + sslHost);
if (host.equals(sslHost)) {
return true;
} else {
return false;
}
}
#Override
public void verify(String host, SSLSocket ssl) throws IOException {
String sslHost = ssl.getInetAddress().getHostName();
System.out.println("Host=" + host);
System.out.println("SSL Host=" + sslHost);
if (host.equals(sslHost)) {
return;
} else {
throw new IOException("hostname in certificate didn't match: " + host + " != " + sslHost);
}
}
#Override
public void verify(String host, X509Certificate cert) throws SSLException {
throw new SSLException("Hostname verification 1 not implemented");
}
#Override
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
throw new SSLException("Hostname verification 2 not implemented");
}
}
Let's test against https://www.rideforrainbows.org/ which is hosted on a shared server.
public static void main (String[] args) throws Exception {
//org.apache.http.conn.ssl.SSLSocketFactory sf = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();
//sf.setHostnameVerifier(new MyHostnameVerifier());
//org.apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf);
org.apache.http.client.HttpClient client = new DefaultHttpClient();
//client.getConnectionManager().getSchemeRegistry().register(sch);
org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/");
org.apache.http.HttpResponse response = client.execute(post);
java.io.InputStream is = response.getEntity().getContent();
java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is));
String line;
while ((line = rd.readLine()) != null) {
System.out.println(line);
}
}
SSLException:
Exception in thread "main" javax.net.ssl.SSLException: hostname in certificate didn't match: www.rideforrainbows.org != stac.rt.sg OR stac.rt.sg OR www.stac.rt.sg
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:231)
...
Do with MyHostnameVerifier:
public static void main (String[] args) throws Exception {
org.apache.http.conn.ssl.SSLSocketFactory sf = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();
sf.setHostnameVerifier(new MyHostnameVerifier());
org.apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf);
org.apache.http.client.HttpClient client = new DefaultHttpClient();
client.getConnectionManager().getSchemeRegistry().register(sch);
org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/");
org.apache.http.HttpResponse response = client.execute(post);
java.io.InputStream is = response.getEntity().getContent();
java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is));
String line;
while ((line = rd.readLine()) != null) {
System.out.println(line);
}
}
Shows:
Host=www.rideforrainbows.org
SSL Host=www.rideforrainbows.org
At least I have the logic to compare (Host == SSL Host) and return true.
The above source code is working for httpclient-4.2.3.jar and httpclient-4.3.3.jar.

Updating the java version from 1.8.0_40 to 1.8.0_181 resolved the issue.

SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();

Related

Apache httpclient 4.3.3 how do I accept only one specific self signed certificate

I'm building a rest client which should only accept responses with one specific certificate. I tried to use a response Interceptor to compare a hash to identify and check that the right certificate is used. But I don't know how to get the server certificate from the response. The methods I found are all deprecated in httpclient 4.3.3.
CloseableHttpClient httpclient = HttpClients.custom().addInterceptorLast(new HttpResponseInterceptor() {
#Override
public void process(HttpResponse response, HttpContext context) throws HttpException, IOException
{
//how do I get the certificate here?
String sha1Hex = DigestUtils.sha1Hex(cert.getEncoded());
boolean check = sha1Hex.equals("xxxxxxxx");
}
}).setSSLSocketFactory(sslsf).build();
Or is there a better way to do this?
This is how thus can be done
CloseableHttpClient httpclient = HttpClients.custom().addInterceptorLast(new HttpResponseInterceptor() {
#Override
public void process(
HttpResponse response, HttpContext context) throws HttpException, IOException {
HttpCoreContext coreContext = HttpCoreContext.adapt(context);
ManagedHttpClientConnection conn = coreContext.getConnection(ManagedHttpClientConnection.class);
SSLSession sslSession = conn.getSSLSession();
if (sslSession != null) {
X509Certificate[] certs = sslSession.getPeerCertificateChain();
if (certs.length == 1) {
String sha1Hex = null;
try {
sha1Hex = DigestUtils.sha1Hex(certs[0].getEncoded());
} catch (CertificateEncodingException ex) {
throw new HttpException("Messged up cert", ex);
}
boolean check = sha1Hex.equals("xxxxxxxx");
}
}
}
}).setSSLSocketFactory(sslsf).build();
However, a better way to accomplish the same, which I would recommend, is to initialize the client's SSL context with trust material consisting of the certificate you want your client to trust.

for rest easy https calls, how to accept all certs

i am trying to call the REST service using jboss rest easy in the following way
public ETTestCasePackage getPackageById(String packageId) throws PackageNotFound {
ClientRequest req = new ClientRequest("https://facebook/api");
req.header("Authorization", "Basic " + EztrackerConstants.base64AuthenticationValue);
req.pathParameter("id", packageId);
ETTestCasePackage etPackage = null;
try {
logger.info("invoking "+req.getUri());
//ProxyFactory.create
ClientResponse<ETTestCasePackage> res = req.get(ETTestCasePackage.class);
etPackage = res.getEntity();
} catch (Exception e) {
logger.debug("Not able to retrieve details for testcase package having id = " + packageId, e);
throw new PackageNotFound("Package with id " + packageId + " not found", e);
}
return etPackage;
}
but the above code obviously throw "peer not authenticated";
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
at sun.security.ssl.SSLSessionImpl.getPeerCertificates(Unknown Source)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:126)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:437)
at
I can add the respective cert to my local java security jks to solve this.
but i may run this so many machines, so cannot do that to all machines. so i want to make my http client accept all request by overridding the http checks.
but for rest easy httprequest, i am not able to find a way to do this. would some one help me in doing for this rest easy.
Thanks in Advance,
syam.
I have tried this piece of code calling the actual code for ignoring but still didn't override the default settings. any idea for to make it work for this rest easy client.
private void test(){
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
}
}
static {
//for localhost testing only
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
return true;
}
});
}
}
Use signed certs as a plan A. As a plan B, when targeting a staging version of another system that you do not control for example, you can use the following solution.
For Resteasy 3, you need to provide your own all-trusting Httpclient to the client instance.
Of course you should never use that in production, so make sure not to hardoce it.
Normally (using jax-rs 2.0) you'd initialize a client like this:
javax.ws.rs.client.Client client = javax.ws.rs.client.ClientBuilder.newClient();
For all trusting client, replace it as follows:
Client client = null;
if (config.trustAllCertificates) {
log.warn("Trusting all certificates. Do not use in production mode!");
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(createAllTrustingClient());
client = new ResteasyClientBuilder().httpEngine(engine).build();
}
else {
client = ClientBuilder.newClient();
}
The createAllTrustingClient() would look like this:
private DefaultHttpClient createAllTrustingClient() throws GeneralSecurityException {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
TrustStrategy trustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
};
SSLSocketFactory factory = new SSLSocketFactory(trustStrategy, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER );
registry.register(new Scheme("https", 443, factory));
ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(registry);
mgr.setMaxTotal(1000);
mgr.setDefaultMaxPerRoute(1000);
DefaultHttpClient client = new DefaultHttpClient(mgr, new DefaultHttpClient().getParams());
return client;
}
Just in case you have trouble figuring out the package names of the classes, here are the relevant imports:
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
For reference:
https://docs.jboss.org/resteasy/docs/3.0-beta-3/userguide/html/RESTEasy_Client_Framework.html#transport_layer
The easiest method is to get a proper certificate, with a correct DN and signed by a public CA, on each machine on which you are deploying the service. It's bureaucratic and annoying and probably costs real money, but it is definitely easiest overall.
Otherwise, you have to configure the clients to have a verifier that doesn't actually verify. That's dangerous, since anyone at all (including random hackers, organised criminals and dodgy government agencies) can make a self-signed certificate and there's no practical way to detect that they have done so. Except by going through and distributing to every client the entire list of server certificates that will ever be used (allowing the verifier to do its check using the club doorman technique: “if you're not on the list, you're not coming in”).
The verifier is technically going to be some kind of instance of X509TrustManager.
To add up on Arnelism's answer: if you are using httpclient-4.2.6.jar (which is a dependency for resteasy-jaxrs-3.0.10.Final.jar), you will find that ThreadSafeClientConnManager is #Deprecated. You can modify it to BasicClientConnectionManager or PoolingClientConnectionManager instead:
private static DefaultHttpClient createAllTrustingClient()
throws GeneralSecurityException {
SchemeRegistry registry = new SchemeRegistry();
registry.register(
new Scheme("http", 80, PlainSocketFactory.getSocketFactory())
);
TrustStrategy trustStrategy = new TrustStrategy() {
#Override
public boolean isTrusted(java.security.cert.X509Certificate[] arg0,
String arg1) throws java.security.cert.CertificateException {
return true;
}
};
SSLSocketFactory factory = new SSLSocketFactory(
trustStrategy,
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
);
registry.register(new Scheme("https", 443, factory));
BasicClientConnectionManager mgr = new BasicClientConnectionManager(registry);
DefaultHttpClient client =
new DefaultHttpClient(mgr, new DefaultHttpClient().getParams());
return client;
}
It's necessary to hack the ApacheHttpClient4Executor, the code below is work with HTTPS and will provide a ClientRequest:
UriBuilder uri = UriBuilder.fromUri(request.endpoint() + request.path());
System.out.println(request.endpoint() + request.path());
class ApacheHttpClient4Executor2 extends ApacheHttpClient4Executor {
}
ApacheHttpClient4Executor2 executor = new ApacheHttpClient4Executor2();
Scheme http = new Scheme("http", 80, PlainSocketFactory.getSocketFactory());
TrustStrategy trustStrategy = new TrustStrategy() {
#Override
public boolean isTrusted(java.security.cert.X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
};
SSLSocketFactory factory = null;
try {
factory = new SSLSocketFactory(trustStrategy, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (KeyManagementException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e1) {
e1.printStackTrace();
}
Scheme https = new Scheme("https", 443, factory);
executor.getHttpClient().getConnectionManager().getSchemeRegistry().register(http);
executor.getHttpClient().getConnectionManager().getSchemeRegistry().register(https);
ClientRequest client = new ClientRequest(uri, executor, providerFactory);

how to ignore self signed certificate and supress peer not authenticated error

I'm trying to send a Https Post request to a server that is using a self signed certificate and i'm receving an exception with the error: peer not authenticated.
I googled and found that the cause of the problem is that the server is using a self signed ceritficate. how can I supress this error ?
I'm using the following function to send the post request:
public String sendPost(final String request, final String postData) throws ClientProtocolException, IOException {
String result = null;
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(request);
ByteArrayEntity postDataEntity = new ByteArrayEntity(postData.getBytes());
httpPost.setEntity(postDataEntity);
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity);
EntityUtils.consume(entity);
} finally {
response.close();
}
return result;
}
what am I missing to supress this error ? I don't want to try and catch this exception.
I want to configure it properly so self signed certificate will be accepted. I'm using
Httpclient 4.1.
thank you!
Many answers that you will find to this question on the web (including ufk's answer) will work, but are not at all secure, because they completely ignore the self-signed server certificate.
This removes much of the benefit of an SSL connection, and opens you up to a man-in-the-middle attack.
What you probably want to do instead is to trust a specific self-signed server certificate, rather than blindly accepting any server certificate.
The key to this is putting a copy of the server's certificate chain into the trust store when creating the SSL context.
The code for doing this is a bit too long to post here, but as it happens, I'm currently working on a blog post about doing this on Android. The blog post isn't published yet, but the sample code is available on GitHub.
public String sendPost(final String request, final String postData) throws ClientProtocolException, IOException, NoSuchAlgorithmException, KeyManagementException {
String result = null;
SSLContext sslContext = SSLContext.getInstance("SSL");
// set up a TrustManager that trusts everything
sslContext.init(null, new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
System.out.println("getAcceptedIssuers =============");
return null;
}
public void checkClientTrusted(X509Certificate[] certs,
String authType) {
System.out.println("checkClientTrusted =============");
}
public void checkServerTrusted(X509Certificate[] certs,
String authType) {
System.out.println("checkServerTrusted =============");
}
} }, new SecureRandom());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(new SSLSocketFactory(sslContext)).build();
HttpPost httpPost = new HttpPost(request);
ByteArrayEntity postDataEntity = new ByteArrayEntity(postData.getBytes());
httpPost.setEntity(postDataEntity);
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity);
EntityUtils.consume(entity);
} finally {
response.close();
}
return result;
}

How to use a custom socketfactory in Apache HttpComponents

I have been trying to use a custom SocketFactory in the httpclient library from the Apache HTTPComponents project. So far without luck. I was expecting that I could just set a socket factory for a HttpClient instance, but it is obviously not so easy.
The documentation for HttpComponents at http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html does mention socket factories, but does not say how to use them.
Does anybody know how this is done?
oleg's answer is of course correct, I just wanted to put the information directly here, in case the link goes bad. In the code that creates a HttpClient, I use this code to let it use my socket factory:
CustomSocketFactory socketFactory = new CustomSocketFactory();
Scheme scheme = new Scheme("http", 80, socketFactory);
httpclient.getConnectionManager().getSchemeRegistry().register(scheme);
CustomSocketFactory is my own socket factory, and I want to use it for normal HTTP traffic, that's why I use "http" and 80 as parameters.
My CustomSchemeSocketFactory looks similar to this:
public class CustomSchemeSocketFactory implements SchemeSocketFactory {
#Override
public Socket connectSocket( Socket socket, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpParams params ) throws IOException, UnknownHostException, ConnectTimeoutException {
if (localAddress != null) {
socket.setReuseAddress(HttpConnectionParams.getSoReuseaddr(params));
socket.bind(localAddress);
}
int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
int soTimeout = HttpConnectionParams.getSoTimeout(params);
try {
socket.setSoTimeout(soTimeout);
socket.connect(remoteAddress, connTimeout );
} catch (SocketTimeoutException ex) {
throw new ConnectTimeoutException("Connect to " + remoteAddress + " timed out");
}
return socket;
}
#Override
public Socket createSocket( HttpParams params ) throws IOException {
// create my own socket and return it
}
#Override
public boolean isSecure( Socket socket ) throws IllegalArgumentException {
return false;
}
}
We use a custom socket factory to allow HttpClient connections to connect to HTTPS URLs with untrusted certificates.
Here is how we did it:
We adapted implementations of both the 'EasySSLProtocolSocketFactory' and 'EasyX509TrustManager' classes from the examples source directory referenced by Oleg.
In our HttpClient startup code, we do the following to enable the new socket factory:
HttpClient httpClient = new HttpClient();
Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
Protocol.registerProtocol("https", easyhttps);
So that any time we request an https:// URL, this socket factory is used.

How to handle invalid SSL certificates with Apache HttpClient? [duplicate]

This question already has answers here:
Resolving javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed Error?
(33 answers)
Closed 3 years ago.
I know, there are many different questions and so many answers about this problem... But I can't understand...
I have: ubuntu-9.10-desktop-amd64 + NetBeans6.7.1 installed "as is" from off. rep.
I need connecting to some site over the HTTPS. For this I use Apache's HttpClient.
From tutorial I read:
"Once you have JSSE correctly installed, secure HTTP communication over SSL should be as
simple as plain HTTP communication." And some example:
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.verisign.com/");
try {
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine());
} finally {
httpget.releaseConnection();
}
By now, I write this:
HttpClient client = new HttpClient();
HttpMethod get = new GetMethod("https://mms.nw.ru");
//get.setDoAuthentication(true);
try {
int status = client.executeMethod(get);
System.out.println(status);
BufferedInputStream is = new BufferedInputStream(get.getResponseBodyAsStream());
int r=0;byte[] buf = new byte[10];
while((r = is.read(buf)) > 0) {
System.out.write(buf,0,r);
}
} catch(Exception ex) {
ex.printStackTrace();
}
As a result I have a set of errors:
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:1627)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:204)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:198)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:994)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:142)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:533)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:471)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:904)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1132)
at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:643)
at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:78)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at org.apache.commons.httpclient.HttpConnection.flushRequestOutputStream(HttpConnection.java:828)
at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2116)
at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096)
at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398)
at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)
at simpleapachehttp.Main.main(Main.java:41)
Caused by: 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.validator.PKIXValidator.doBuild(PKIXValidator.java:302)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:205)
at sun.security.validator.Validator.validate(Validator.java:235)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:147)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:230)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:270)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:973)
... 17 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:191)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:255)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:297)
... 23 more
What have I to do to create simplest SSL connection?
(Probably without KeyManager and Trust manager etc. while.)
https://mms.nw.ru uses a self-signed certificate that's not in the default trust manager set. To resolve the issue, do one of the following:
Configure SSLContext with a TrustManager that accepts any certificate (see below).
Configure SSLContext with an appropriate trust store that includes your certificate.
Add the certificate for that site to the default Java trust store.
Here's a program that creates a (mostly worthless) SSL Context that accepts any certificate:
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class SSLTest {
public static void main(String [] args) throws Exception {
// configure the SSLContext with a TrustManager
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom());
SSLContext.setDefault(ctx);
URL url = new URL("https://mms.nw.ru");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
System.out.println(conn.getResponseCode());
conn.disconnect();
}
private static class DefaultTrustManager implements X509TrustManager {
#Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
#Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
#Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}
https://mms.nw.ru likely uses a certificate not issued by a certification authority. Consequently, you need to add the certificate to your trusted Java key store as explained in unable to find valid certification path to requested target:
When working on a client that works
with an SSL enabled server running in
https protocol, you could get error
'unable to find valid certification
path to requested target' if the
server certificate is not issued by
certification authority, but a self
signed or issued by a private CMS.
Don't panic. All you need to do is to
add the server certificate to your
trusted Java key store if your client
is written in Java. You might be
wondering how as if you can not access
the machine where the server is
installed. There is a simple program
can help you. Please download the Java
program and run
% java InstallCert _web_site_hostname_
This program opened a connection to
the specified host and started an SSL
handshake. It printed the exception
stack trace of the error that occured
and shows you the certificates used by
the server. Now it prompts you add the
certificate to your trusted KeyStore.
If you've changed your mind, enter
'q'. If you really want to add the
certificate, enter '1', or other
numbers to add other certificates,
even a CA certificate, but you usually
don't want to do that. Once you have
made your choice, the program will
display the complete certificate and
then added it to a Java KeyStore named
'jssecacerts' in the current
directory.
To use it in your program, either
configure JSSE to use it as its trust
store or copy it into your
$JAVA_HOME/jre/lib/security directory.
If you want all Java applications to
recognize the certificate as trusted
and not just JSSE, you could also
overwrite the cacerts file in that
directory.
After all that, JSSE will be able to
complete a handshake with the host,
which you can verify by running the
program again.
To get more details, you can check out
Leeland's blog No more 'unable to find
valid certification path to requested
target'
In addition to Pascal Thivent's correct answer, another way is to save the certificate from Firefox (View Certificate -> Details -> export) or openssl s_client and import it into the trust store.
You should only do this if you have a way to verify that certificate. Failing that, do it the first time you connect, it will at least give you an error if the certificate changes unexpectedly on subsequent connections.
To import it in a trust store, use:
keytool -importcert -keystore truststore.jks -file servercert.pem
By default, the default trust store should be $JAVA_HOME/jre/lib/security/cacerts and its password should be changeit, see JSSE Reference guide for details.
If you don't want to allow that certificate globally, but only for these connections, it's possible to create an SSLContext for it:
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream("/.../truststore.jks");
ks.load(fis, null);
// or ks.load(fis, "thepassword".toCharArray());
fis.close();
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
Then, you need to set it up for Apache HTTP Client 3.x by implementing one if its SecureProtocolSocketFactory to use this SSLContext. (There are examples here).
Apache HTTP Client 4.x (apart from the earliest version) has direct support for passing an SSLContext.
For Apache HttpClient 4.5+ & Java8:
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial((chain, authType) -> true).build();
SSLConnectionSocketFactory sslConnectionSocketFactory =
new SSLConnectionSocketFactory(sslContext, new String[]
{"SSLv2Hello", "SSLv3", "TLSv1","TLSv1.1", "TLSv1.2" }, null,
NoopHostnameVerifier.INSTANCE);
CloseableHttpClient client = HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
But if your HttpClient use a ConnectionManager for seeking connection, e.g. like this:
PoolingHttpClientConnectionManager connectionManager = new
PoolingHttpClientConnectionManager();
CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
The HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory) has no effect, the problem is not resolved.
Because that the HttpClient use the specified connectionManager for seeking connection and the specified connectionManager haven't register our customized SSLConnectionSocketFactory. To resolve this, should register the The customized SSLConnectionSocketFactory in the connectionManager. The correct code should like this:
PoolingHttpClientConnectionManager connectionManager = new
PoolingHttpClientConnectionManager(RegistryBuilder.
<ConnectionSocketFactory>create()
.register("http",PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory).build());
CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
The Apache HttpClient 4.5 way:
org.apache.http.ssl.SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
sslContextBuilder.loadTrustMaterial(new org.apache.http.conn.ssl.TrustSelfSignedStrategy());
SSLContext sslContext = sslContextBuilder.build();
org.apache.http.conn.ssl.SSLConnectionSocketFactory sslSocketFactory =
new SSLConnectionSocketFactory(sslContext, new org.apache.http.conn.ssl.DefaultHostnameVerifier());
HttpClientBuilder httpClientBuilder = HttpClients.custom().setSSLSocketFactory(sslSocketFactory);
httpClient = httpClientBuilder.build();
NOTE: org.apache.http.conn.ssl.SSLContextBuilder is deprecated and org.apache.http.ssl.SSLContextBuilder is the new one (notice conn missing from the latter's package name).
From http://hc.apache.org/httpclient-3.x/sslguide.html:
Protocol.registerProtocol("https",
new Protocol("https", new MySSLSocketFactory(), 443));
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.whatever.com/");
try {
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine());
} finally {
httpget.releaseConnection();
}
Where MySSLSocketFactory example can be found here. It references a TrustManager, which you can modify to trust everything (although you must consider this!)
want to paste the answer here:
in Apache HttpClient 4.5.5
How to handle invalid SSL certificate with Apache client 4.5.5?
HttpClient httpClient = HttpClients
.custom()
.setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build())
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
Once you have a Java Cert Store (by using the great InstallCert class created above), you can get java to use it by passing the "javax.net.ssl.trustStore" param at java startup.
Ex:
java -Djavax.net.ssl.trustStore=/path/to/jssecacerts MyClassName
Another issue you may run into with self signed test certs is this:
java.io.IOException: HTTPS hostname wrong: should be ...
This error occurs when you are trying to access a HTTPS url. You might have already installed the server certificate to your JRE's keystore. But this error means that the name of the server certificate does not match with the actual domain name of the server that is mentioned in the URL. This normally happens when you are using a non CA issued certificate.
This example shows how to write a HttpsURLConnection DefaultHostnameVerifier that ignore the certificates server name:
http://www.java-samples.com/showtutorial.php?tutorialid=211
EasySSLProtocolSocketFactory was giving me problems so I ended up implementing my own ProtocolSocketFactory.
First you need to register it:
Protocol.registerProtocol("https", new Protocol("https", new TrustAllSSLSocketFactory(), 443));
HttpClient client = new HttpClient();
...
Then implement ProtocolSocketFactory:
class TrustAllSSLSocketFactory implements ProtocolSocketFactory {
public static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{
new X509TrustManager() {
public void checkClientTrusted(final X509Certificate[] certs, final String authType) {
}
public void checkServerTrusted(final X509Certificate[] certs, final String authType) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
};
private TrustManager[] getTrustManager() {
return TRUST_ALL_CERTS;
}
public Socket createSocket(final String host, final int port, final InetAddress clientHost,
final int clientPort) throws IOException {
return getSocketFactory().createSocket(host, port, clientHost, clientPort);
}
#Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress,
final int localPort, final HttpConnectionParams params) throws IOException {
return createSocket(host, port);
}
public Socket createSocket(final String host, final int port) throws IOException {
return getSocketFactory().createSocket(host, port);
}
private SocketFactory getSocketFactory() throws UnknownHostException {
TrustManager[] trustAllCerts = getTrustManager();
try {
SSLContext context = SSLContext.getInstance("SSL");
context.init(null, trustAllCerts, new SecureRandom());
final SSLSocketFactory socketFactory = context.getSocketFactory();
HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);
return socketFactory;
} catch (NoSuchAlgorithmException | KeyManagementException exception) {
throw new UnknownHostException(exception.getMessage());
}
}
}
Note: This is with HttpClient 3.1 and Java 8
For a way to easily add hosts you trust at runtime without throwing out all checks, try the code here: http://code.google.com/p/self-signed-cert-trust-manager/.
I happened to face the same issue, all of a sudden all my imports were missing. I tried deleting all the contents in my .m2 folder. And trying to re-import everything , but still nothing worked.
Finally what I did was opened the website for which the IDE was complaining that it couldn't download in my browser. And saw the certificate it was using, and saw in my
$ keytool -v -list PATH_TO_JAVA_KEYSTORE
Path to my keystore was /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
that particular certificate was not there.
So all you have to do is put the certificate into the JAVA JVM keystore again.
It can be done using the below command.
$ keytool -import -alias ANY_NAME_YOU_WANT_TO_GIVE -file PATH_TO_YOUR_CERTIFICATE -keystore PATH_OF_JAVA_KEYSTORE
If it asks for password, try the default password 'changeit'
If you get permission error when running the above command.
In windows open it in administration mode.
In mac and unix use sudo.
After you have successfully added the key,
You can view it using :
$ keytool -v -list /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
You can view just the SHA-1 using teh command
$ keytool -list /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
This link explains the requirement you have step by step. If You are not really concerned which certificate you can proceed with the process in below link.
Note You might want to double check what you are doing since, this is a unsafe operation.
Using the InstallCert to generate the jssecacerts file and do
-Djavax.net.ssl.trustStore=/path/to/jssecacerts worked great.
I'm useing httpclient 3.1.X ,and this works for me
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManager trustManager = new X509TrustManager() {
#Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[]{trustManager}, null);
SslContextSecureProtocolSocketFactory socketFactory = new SslContextSecureProtocolSocketFactory(sslContext,false);
Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory) socketFactory, 443));//同样会影响到HttpUtils
} catch (Throwable e) {
e.printStackTrace();
}
public class SslContextSecureProtocolSocketFactory implements SecureProtocolSocketFactory {
private SSLContext sslContext;
private boolean verifyHostname;
public SslContextSecureProtocolSocketFactory(SSLContext sslContext, boolean verifyHostname) {
this.verifyHostname = true;
this.sslContext = sslContext;
this.verifyHostname = verifyHostname;
}
public SslContextSecureProtocolSocketFactory(SSLContext sslContext) {
this(sslContext, true);
}
public SslContextSecureProtocolSocketFactory(boolean verifyHostname) {
this((SSLContext)null, verifyHostname);
}
public SslContextSecureProtocolSocketFactory() {
this((SSLContext)null, true);
}
public synchronized void setHostnameVerification(boolean verifyHostname) {
this.verifyHostname = verifyHostname;
}
public synchronized boolean getHostnameVerification() {
return this.verifyHostname;
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException {
SSLSocketFactory sf = this.getSslSocketFactory();
SSLSocket sslSocket = (SSLSocket)sf.createSocket(host, port, clientHost, clientPort);
this.verifyHostname(sslSocket);
return sslSocket;
}
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
if(params == null) {
throw new IllegalArgumentException("Parameters may not be null");
} else {
int timeout = params.getConnectionTimeout();
Socket socket = null;
SSLSocketFactory socketfactory = this.getSslSocketFactory();
if(timeout == 0) {
socket = socketfactory.createSocket(host, port, localAddress, localPort);
} else {
socket = socketfactory.createSocket();
InetSocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
InetSocketAddress remoteaddr = new InetSocketAddress(host, port);
socket.bind(localaddr);
socket.connect(remoteaddr, timeout);
}
this.verifyHostname((SSLSocket)socket);
return socket;
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
SSLSocketFactory sf = this.getSslSocketFactory();
SSLSocket sslSocket = (SSLSocket)sf.createSocket(host, port);
this.verifyHostname(sslSocket);
return sslSocket;
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
SSLSocketFactory sf = this.getSslSocketFactory();
SSLSocket sslSocket = (SSLSocket)sf.createSocket(socket, host, port, autoClose);
this.verifyHostname(sslSocket);
return sslSocket;
}
private void verifyHostname(SSLSocket socket) throws SSLPeerUnverifiedException, UnknownHostException {
synchronized(this) {
if(!this.verifyHostname) {
return;
}
}
SSLSession session = socket.getSession();
String hostname = session.getPeerHost();
try {
InetAddress.getByName(hostname);
} catch (UnknownHostException var10) {
throw new UnknownHostException("Could not resolve SSL sessions server hostname: " + hostname);
}
X509Certificate[] certs = (X509Certificate[])((X509Certificate[])session.getPeerCertificates());
if(certs != null && certs.length != 0) {
X500Principal subjectDN = certs[0].getSubjectX500Principal();
List cns = this.getCNs(subjectDN);
boolean foundHostName = false;
Iterator i$ = cns.iterator();
AntPathMatcher matcher = new AntPathMatcher();
while(i$.hasNext()) {
String cn = (String)i$.next();
if(matcher.match(cn.toLowerCase(),hostname.toLowerCase())) {
foundHostName = true;
break;
}
}
if(!foundHostName) {
throw new SSLPeerUnverifiedException("HTTPS hostname invalid: expected \'" + hostname + "\', received \'" + cns + "\'");
}
} else {
throw new SSLPeerUnverifiedException("No server certificates found!");
}
}
private List<String> getCNs(X500Principal subjectDN) {
ArrayList cns = new ArrayList();
StringTokenizer st = new StringTokenizer(subjectDN.getName(), ",");
while(st.hasMoreTokens()) {
String cnField = st.nextToken();
if(cnField.startsWith("CN=")) {
cns.add(cnField.substring(3));
}
}
return cns;
}
protected SSLSocketFactory getSslSocketFactory() {
SSLSocketFactory sslSocketFactory = null;
synchronized(this) {
if(this.sslContext != null) {
sslSocketFactory = this.sslContext.getSocketFactory();
}
}
if(sslSocketFactory == null) {
sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
}
return sslSocketFactory;
}
public synchronized void setSSLContext(SSLContext sslContext) {
this.sslContext = sslContext;
}
}
For HttpClient, we can do this :
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom());
SSLContext.setDefault(ctx);
String uri = new StringBuilder("url").toString();
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
};
HttpClient client = HttpClientBuilder.create().setSSLContext(ctx)
.setSSLHostnameVerifier(hostnameVerifier).build()
follow the instruction given below for Java 1.7, to create an SSL certificate using InstallCert.java program file.
https://github.com/escline/InstallCert
you must restart the tomcat
Used the following along with DefaultTrustManager and it worked in httpclient like charm. Thanks a ton!! #Kevin and every other contributor
SSLContext ctx = null;
SSLConnectionSocketFactory sslsf = null;
try {
ctx = SSLContext.getInstance("TLS");
ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom());
SSLContext.setDefault(ctx);
sslsf = new SSLConnectionSocketFactory(
ctx,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
} catch (Exception e) {
e.printStackTrace();
}
CloseableHttpClient client = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();

Categories