How to ignore an invalid SSL certificate in Java?

Sometimes during development it is useful to use a certificate whose CN (Common Name) does not match the host name in the URL, for example localhost. In these cases Java will throw a SSLHandshakeException. How can we easily disable certificate checking for localhost and other domains of our choosing?

Your own certificate checking

The easiest way is to create your own class that implements the interface HostnameVerifier:

WhitelistHostnameVerifier.java

package com.relentlesscoding.https;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import java.util.HashSet;
import java.util.Set;

// singleton
enum WhitelistHostnameVerifier implements HostnameVerifier {
    // these hosts get whitelisted
    INSTANCE("localhost", "sub.domain.com");

    private Set whitelist = new HashSet<>();
    private HostnameVerifier defaultHostnameVerifier =
            HttpsURLConnection.getDefaultHostnameVerifier();

    WhitelistHostnameVerifier(String... hostnames) {
        for (String hostname : hostnames) {
            whitelist.add(hostname);
        }
    }

    @Override
    public boolean verify(String host, SSLSession session) {
        if (whitelist.contains(host)) {
            return true;
        }
        // important: use default verifier for all other hosts
        return defaultHostnameVerifier.verify(host, session);
    }
}

Main.java

// use our HostnameVerifier for all future connections
HttpsURLConnection.setDefaultHostnameVerifier(
        WhitelistHostnameVerifier.INSTANCE);
final String url = "https://relentlesscoding.com";
HttpsURLConnection conn =
        (HttpsURLConnection) new URL(url).openConnection();
System.out.println(conn.getResponseCode());

HttpsURLConnection.DefaultHostnameVerifier#verify always returns false, so we might as well return false ourselves, but this way the code will keep working when the implementation of HttpsURLConnection changes.

Note: you can set the default HostnameVerifier globally by using the static method setDefaultHostnameVerifier, or you can set it per instance, using conn.setHostnameVerifier.

Problems with Let’s Encrypt certificates

One thing I found out while testing this, was that OpenJDK version 1.8.0_131 won’t correctly handle Let’s Encrypt certificates:

java.io.IOException: HTTPS hostname wrong: should be <relentlesscoding.com>

This is due to the fact that Let’s Encrypt certificates are not present in the Java installation. Upgrading to version 1.8.0_141 resolves this (for Oracle versions >= 8U101 or >= 7U111).

Whitelisting, NOT disabling certificate checking

The best approach here is whitelisting. This will keep the certificate checking in place for most sites, and will only disable it for pre-approved hosts. I saw quite a bit of examples online that disable certificate checking completely, by writing the verify method as follows:

public boolean verify(String hostname, SSLSession session) {
    return true;
}

This is not secure, as it completely ignores all invalid certificates.

Java stateful sessions or: how to properly send cookies with each redirect request

The problem

I needed to login to some webpage programmatically. It happened to be one of those pages where, when the login succeeds, the user is redirected to another page, and then to another (something along the lines of ‘Please login’ -> ‘You are successfully logged in’ -> ‘Admin panel’). So I needed to write something that would store the cookies that come along with each response, and send those cookies out with each subsequent request. With curl, I would have used:

$ curl --location --cookie-jar logincookie 'https://login.securepage.com'

So how can we mock this behavior in Java?

Java SE’s CookieHandler

A simple DuckDuckGo search will show Java SE’s own java.net.CookieHandler: pretty neat, a built-in solution! The CookieHandler, as the name suggests, will handle all your floating-point arithmetic. Just kidding, it will serve as the object where the HTTP protocol handler (used by objects such as URL) will go to to check for relevant cookies. CookieHandler is a global abstract class that will handle the state management of all requests.

CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
CookieHandler.setDefault(cookieManager);

CookieManager’s task is to decide which cookies to accept and which to reject. CookiePolicy.ACCEPT_ALL will accept all cookies. Other choices include CorkiePolicy.ACCEPT_NONE (no cookies will be accepted) and CookiePolicy.ACCEPT_ORIGINAL_SERVER (only cookies from the original server will be accepted).

If you want, you can also create a CookieManager with a specific CookieStore and specify exactly where your cookies will be stored. This is needed, for example, to create persistent cookies that can be restored after a JVM restart. By default, CookieManager will create an in-memory cookie store.

To install our own system-wide cookie handler, we invoke CookieHandler.setDefault and pass our CookieManager as an argument.

The tutorial on the Oracle website does a pretty decent job of explaining everything in more detail.

If this works for you, great! If not, keep on reading.

Apache HttpComponent Client to the rescue

Unfortunately, the steps above did not work for me as the server kept complaining that the session was invalid after the redirects. After reading somewhere on StackOverflow that Java SE’s version of the CookieStore was buggy, and me being on a deadline, I decided to turn my attention to Apache and their HttpComponent Client (version 4.5.3):

import org.apache.http.client.CookieStore;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
// ... other imports

class Downloader {
    private CookieStore cookieStore = new BasicCookieStore();

    private void doLogin() {
        // CookieSpecs.STANDARD is the RFC 6265 compliant policy
        RequestConfig requestConfig = RequestConfig
                .custom()
                .setCookieSpec(CookieSpecs.STANDARD)
                .build();

        // automatically follow redirects
        CloseableHttpClient client = HttpClients
                .custom()
                .setRedirectStrategy(new LaxRedirectStrategy())
                .setDefaultRequestConfig(requestConfig)
                .setDefaultCookieStore(cookieStore)
                .build();

        String addr = "https://login.securehost.com";
        HttpPost post = new HttpPost();

        // login and password as payload for POST request
        List<NameValuePair> urlParams = new ArrayList<>();
        urlParams.add(new BasicNameValuePair("login", "neftas"));
        urlParams.add(new BasicNameValuePair("password", "letmein"));
        post.setEntity(new UrlEncodedFormEntity(urlParams));

        HttpResponse response = client.execute(post);
        // ... and we are logged in!
    }
}

(To keep the code clean, I’ve not bothered with any exception handling.)

The RequestConfig serves to indicate what kind of cookie policy we want (there are several, unfortunately). If you just want things to work, you can start with CookieSpecs.STANDARD, which is compliant with “a more relaxed profile defined by RFC 6265”.

During the creation of the HttpClient, we specify that we want to follow all redirects (setRedirectStrategy(new LaxRedirectStrategy())), we pass the cookie policy we defined earlier in the RequestConfig, and lastly, we pass the cookie store.