I ran across a fairly harmless looking routine in the Java API at work today that is actually quite dangerous. Making a note and sharing my findings to prevent hitting it again! Performing secure networking in Java is a bit of a burden, especially in testing and during development when you just want things to work. The "fix it quick" mentality in this case will come back to bite hard in this case though! For getting things started, most blogs and news articles will demonstrate how to set up a "permissive" HostnameVerifier and TrustManager like this:

== PermissiveHostnameVerifier.java ==
import javax.net.ssl.HostnameVerifier;

class PermissiveHostnameVerifier implements HostnameVerifier {
    @Override
    public boolean verify(String urlHostname, String certHostname) {
        return true;
    }
}
== PermissiveTrustManager.java ==
import javax.net.ssl.X509TrustManager;    
import java.security.cert.X509Certificate;

class PermissiveTrustManager implements X509TrustManager {
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
    
    @Override
    public void checkClientTrusted(X509Certificate[] certs, String authType) {}
    
    @Override
    public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
== Main.java ==
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;

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

public class Main {
    private static int TIMEOUT = 5000;
    
    public static void main(String[] args) {
        setSSLPermissive();
        read("https://facebook.com");
    }
    
    private static void setSSLPermissive() {
        try {
            System.out.println("Setting SSL to permissive");
            SSLContext sc = SSLContext.getInstance("SSL");
            TrustManager[] trustAllCerts = new TrustManager[]{new PermissiveTrustManager()};
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(new PermissiveHostnameVerifier());
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
    }
    
    private static void read(String path) {
        try {
            System.out.println("Attempting to read: " + path);
            URL url = new URL(path);
            HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
            conn.setReadTimeout(TIMEOUT);
            conn.connect();
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = "";
            while((line = br.readLine()) != null) {
                System.out.println(line);
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }
}

This kind of code is obviously intended for testing and debugging! The most troublesome bit being the Main::setSSLPermisive method since it sets the dirty deed in motion. I ran across HttpsURLConnection.setDefaultSSLSocketFactory and setDefaultHostnameVerifier in our codebase at first thinking whoever used it knew what they were doing. In light of benefit of doubt, I thought perhaps these methods set the default on a per webapp context or thread basis. Unfortunately, this is not the case! A bit of experimenting with some test webapps revealed that using these methods sets the default for the entire jvm!

This is bad for two reasons:

  1. If you set the default to something permissive, other webapps on the system that rely on true security have the rug pulled out from under them
  2. If you are relying on the defaults, another webapp could come along and change it to something more restrictive or incompatible at any time

Used resposibly, this bit of code can save time, but flag it and don't let it hit production without some serious thought! The "correct" way to use this is to use the per-instance HttpsURLConnection.setSSLSocketFactory() and HttpsURLConnection.setHostnameVerifier() methods. Setting up a Non-Permissive HostnameVerifier and TrustManager is specific to your business needs. Perhaps later I'll write an article on some examples and outline concepts of how this can be done!