How to Handle Invalid Ssl Certificates with Apache Httpclient

How to handle invalid SSL certificates with Apache HttpClient?

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;
}
}
}

How to ignore SSL certificate errors in Apache HttpClient 4.0

You need to create a SSLContext with your own TrustManager and create HTTPS scheme using this context. Here is the code,

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());

SSLSocketFactory sf = new SSLSocketFactory(sslContext);
Scheme httpsScheme = new Scheme("https", 443, sf);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(httpsScheme);

// apache HttpClient version >4.2 should use BasicClientConnectionManager
ClientConnectionManager cm = new SingleClientConnManager(schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm);

Ignoring SSL certificate in Apache HttpClient 4.3

The code below works for trusting self-signed certificates. You have to use the TrustSelfSignedStrategy when creating your client:

SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
builder.build());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
sslsf).build();

HttpGet httpGet = new HttpGet("https://some-server");
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
} finally {
response.close();
}

I did not include the SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER on purpose: The point was to allow testing with self signed certificates so you don't have to acquire a proper certificate from a certification authority. You can easily create a self-signed certificate with the correct host name, so do that instead of adding the SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER flag.

Ignore self-signed certificates in Apache HTTPClient 4.5

One of the above approaches should work in case of self-signed certificates, but the weird thing is you are getting same exception in all the approaches.

I feel during SSL session establishment or handshaking protocol is not being accepted either by client or by server.

The best solution here is to debug the application.

In case of tomcat, add -Djavax.net.debug=all in setenv.sh or setenv.bat files and then restart the server.

Or you can follow this tutorial.

The OP just needed to change the port when connecting to SSL:

//For HTTPS
HttpHost httpstarget = new HttpHost("mysite.com", 443, "https");

//For HTTP
HttpHost httptarget = new HttpHost("mysite.com", 80, "http");

Apache Http Client SSL certificate error

Please also note that trusting self-signed certs does not mean trusting any arbitrary cert.

Try setting up your SSL context this way:

SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, 
new TrustStrategy() {
@Override
public boolean isTrusted(final X509Certificate[] chain, final String authType)
throws CertificateException {
return true;
}
})
.useTLS()
.build();

Please also note that generally trusting certificates indiscriminately defeats the purpose of using SSL in the first place. Use when absolutely necessary or for testing only

How to ignore SSL certificate error using Apache HTTPClient but log it

It is just a simple matter of decorating X509TrustManager instances passed to the SSLContext#init method

static class TrustManagerDelegate implements X509TrustManager {

private final X509TrustManager trustManager;

TrustManagerDelegate(final X509TrustManager trustManager) {
super();
this.trustManager = trustManager;
}

@Override
public void checkClientTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
trustManager.checkClientTrusted(chain, authType);
}

@Override
public void checkServerTrusted(
final X509Certificate[] chain, final String authType) {
try {
trustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ex) {
// Implement proper logging;
System.out.println(chain[0]);
ex.printStackTrace(System.out);
}
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return trustManager.getAcceptedIssuers();
}

}

...

TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmfactory.init((KeyStore) null);
final TrustManager[] tms = tmfactory.getTrustManagers();
if (tms != null) {
for (int i = 0; i < tms.length; i++) {
final TrustManager tm = tms[i];
if (tm instanceof X509TrustManager) {
tms[i] = new TrustManagerDelegate((X509TrustManager) tm);
}
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tms, null);

CloseableHttpClient client = HttpClientBuilder.create()
.setSSLContext(sslContext)
.build();

How do I use an SSL client certificate with Apache HttpClient?

I think the main difference is that in java, you usually put the key and the certificate to a key store and use it from there. Like you mention often people do want to use a separate library for it, like mentioned httpcomponents client (just like you're using requests library in your python example).

Here's an example of using a client certificate from a key store, using the previously mentioned library:

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.junit.Test;

import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.security.KeyStore;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class MyClientCertTest {

private static final String KEYSTOREPATH = "/clientkeystore.jks"; // or .p12
private static final String KEYSTOREPASS = "keystorepass";
private static final String KEYPASS = "keypass";

KeyStore readStore() throws Exception {
try (InputStream keyStoreStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
KeyStore keyStore = KeyStore.getInstance("JKS"); // or "PKCS12"
keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray());
return keyStore;
}
}
@Test
public void readKeyStore() throws Exception {
assertNotNull(readStore());
}
@Test
public void performClientRequest() throws Exception {
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(readStore(), KEYPASS.toCharArray()) // use null as second param if you don't have a separate key password
.build();

HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
HttpResponse response = httpClient.execute(new HttpGet("https://slsh.iki.fi/client-certificate/protected/"));
assertEquals(200, response.getStatusLine().getStatusCode());
HttpEntity entity = response.getEntity();

System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
EntityUtils.consume(entity);
}
}

Maven pom for dependency versions:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.acme</groupId>
<artifactId>httptests</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.9</version>
<!-- this is not needed, but useful if you want to debug what's going
on with your connection -->
<configuration>
<argLine>-Djavax.net.debug=all</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>

I've also published a simple test page for testing a client certificate.


Just to demonstrate that it can be done, below is an example of using client certificate just using standard java api, without extra libraries.

import org.junit.Test;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyStore;

public class PlainJavaHTTPS2Test {

@Test
public void testJKSKeyStore() throws Exception {
final String KEYSTOREPATH = "clientkeystore.jks";
final char[] KEYSTOREPASS = "keystorepass".toCharArray();
final char[] KEYPASS = "keypass".toCharArray();

try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
setSSLFactories(storeStream, "JKS", KEYSTOREPASS, KEYPASS);
}
testPlainJavaHTTPS();
}
@Test
public void testP12KeyStore() throws Exception {
final String KEYSTOREPATH = "clientkeystore.p12";
final char[] KEYSTOREPASS = "keystorepass".toCharArray();
final char[] KEYPASS = "keypass".toCharArray();

try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
setSSLFactories(storeStream, "PKCS12", KEYSTOREPASS, KEYPASS);
}
testPlainJavaHTTPS();
}
private static void setSSLFactories(InputStream keyStream, String keystoreType, char[] keyStorePassword, char[] keyPassword) throws Exception
{
KeyStore keyStore = KeyStore.getInstance(keystoreType);

keyStore.load(keyStream, keyStorePassword);

KeyManagerFactory keyFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

keyFactory.init(keyStore, keyPassword);

KeyManager[] keyManagers = keyFactory.getKeyManagers();

SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(keyManagers, null, null);
SSLContext.setDefault(sslContext);
}

public void testPlainJavaHTTPS() throws Exception {
String httpsURL = "https://slsh.iki.fi/client-certificate/protected/";
URL myUrl = new URL(httpsURL);
HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
try (InputStream is = conn.getInputStream()) {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);

String inputLine;

while ((inputLine = br.readLine()) != null) {
System.out.println(inputLine);
}
}
}
}

And here's a third version with least amount of code, but which relies on the fact that a) keystore is a file on disk, not within jar, and b) key password must be identical to keystore password.

import org.junit.BeforeClass;
import org.junit.Test;

import java.net.URL;
import java.io.*;
import javax.net.ssl.HttpsURLConnection;

public class PlainJavaHTTPSTest {

@BeforeClass
public static void setUp() {
System.setProperty("javax.net.ssl.keyStore", "/full/path/to/clientkeystore-samepassword.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "keystorepass");
}

@Test
public void testPlainJavaHTTPS() throws Exception {
String httpsURL = "https://slsh.iki.fi/client-certificate/protected/";
URL myUrl = new URL(httpsURL);
HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
try (InputStream is = conn.getInputStream()) {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);

String inputLine;

while ((inputLine = br.readLine()) != null) {
System.out.println(inputLine);
}
}
}
}

The properties set above in code can of course be also given as startup parameters, -Djavax.net.ssl.keyStore=/full/path/to/clientkeystore-samepassword.jks and -Djavax.net.ssl.keyStorePassword=keystorepass.

Trusting all certificates using HttpClient over HTTPS

Note: Do not implement this in production code you are ever going to use on a network you do not entirely trust. Especially anything going over the public internet.

Your question is just what I want to know. After I did some searches, the conclusion is as follows.

In HttpClient way, you should create a custom class from org.apache.http.conn.ssl.SSLSocketFactory, not the one org.apache.http.conn.ssl.SSLSocketFactory
itself. Some clues can be found in this post Custom SSL handling stopped working on Android 2.2 FroYo.

An example is like ...

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.conn.ssl.SSLSocketFactory;
public class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");

public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);

TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
};

sslContext.init(null, new TrustManager[] { tm }, null);
}

@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}

@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}

and use this class while creating instance of HttpClient.

public HttpClient getNewHttpClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);

MySSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));

ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
}

BTW, the link below is for someone who is looking for HttpURLConnection solution.
Https Connection Android

I have tested the above two kinds of solutions on froyo, and they all work like a charm in my cases. Finally, using HttpURLConnection may face the redirect problems, but this is beyond the topic.

Note: Before you decide to trust all certificates, you probably should know the site full well and won't be harmful of it to end-user.

Indeed, the risk you take should be considered carefully, including the effect of hacker's mock site mentioned in the following comments that I deeply appreciated. In some situation, although it might be hard to take care of all certificates, you'd better know the implicit drawbacks to trust all of them.



Related Topics



Leave a reply



Submit