RSS

Working Around Java’s SSL Limitations

16 Oct

The Java run-time environment (JVM) is able to support connections to other servers using SSL, but it has this very inconvenient behavior of refusing to connect to self-signed servers.  A self signed server has the public key necessary to ensure private communications, but does not have a certificate that proves who it is.  In the default mode of operation, when connecting to such a server, Java SSL subsystem with throw an exception and prevent all communications.  But there are good reasons why a service that you want to communicate with privately may not have a certificate, and this post tells you how to accomplish this.

I discuss on a post titled “The Anti-SSL Conspiracy” how ridiculous this is.  We can think in terms of three levels of security:

  • Level A – no encryption, no verification
  • Level B – encryption, no verification
  • Level C – encryption and verification

Level A is normal HTTP over a non secure socket.  Level B is better than that, because the traffic is encrypted, even though there is no verification of the certificate at the other end.  Finally Level C is best of these, because you have both privacy, as well as some assurance of who you are connecting with.

The designers of the Java subsystem for communications have essentially decided that Level A is acceptable, and that Level C is acceptable, but Level B is prevented by the throwing of an exception.  This makes no sense.  Level B is freely available to anyone who wished to take the time to set it up, and it offers privacy not allowed in Level A.  Level C requires a certificate that must be purchased by a signing authority.  It must be locked to a particular DNS name, and there are other constraints.  All of this adds to the cost which is not trivial.  The cost of getting and maintaining a certificate can be greater than the total cost of hosting a web site without it.  (Compare $5/month rates for HTTP sites, with $15/month or more for HTTPS sites.)

While it is expensive to get a certificate for an otherwise free server, there is a strong argument to make for making all Internet traffic go over SSL.  The more that traffic is encrypted, the safer personal data is, and the harder it is for terrorists and criminals to gather data. I am not a privacy freak, but it really makes sense to have servers talking to each other in a safe way.

While Level B security is better in all situations than Level A security, one difficulty is that it is hard to write Java software to talk to a self-signed server.  instead it will throw the following:

  • javax.net.ssl.SSLException: Not trusted server certificate

Disabling Certificate Validation on Java Libraries

The following class will set the defaults such that no certificates are validated, and you are allowed to proceed to make a SSL connection and exchange data in a private way.

package org.workcast.streams;

import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
* Copyright Keith D Swenson, Creative Commons Share-Alike
* Feel free to use this code where you like, but keep this copyright
* statement in it, and understand that there is no guarantee to be
* suitable for any purpose, so read it carefully.
*/
public class SSLPatch
{

    /**
    * Java proides a standard "trust manager" interface.  This trust manager
    * essentially disables the rejection of certificates by trusting anyone and everyone.
    */
    public static X509TrustManager getDummyTrustManager() {
        return new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        };
    }

    /**
    * Returns a hostname verifiers that always returns true, always positively verifies a host.
    */
    public static HostnameVerifier getAllHostVerifier() {
        return new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
    }

    /**
    * a call to disableSSLCertValidation will disable certificate validation
    * for SSL connection made after this call.   This is installed as the
    * default in the JVM for future calls.  Returns the SSLContext in case
    * you need it for something else.
    */
    public static SSLContext disableSSLCertValidation() throws Exception {

          // Create a trust manager that does not validate certificate chains
        TrustManager[] tma = new TrustManager[] {getDummyTrustManager()};

        // Install the all-trusting trust manager
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, tma, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        // Install the all-trusting host verifier
        HttpsURLConnection.setDefaultHostnameVerifier(getAllHostVerifier());
        return sc;
     }

}

Simply call disableSSLCertValidation() anytime before making an SSL connection.  This is very heavy handed.  Because the library throws an exception, you have to decided to do this BEFORE you know whether the certificate is valid or not.  A better way would be to allow the calling program to simply access a method that told it whether the certificate was valid or not. Then the calling program could decide one way or another whether it wanted to continue or not. But it would take a lot more work to do this.  Maybe I will get around to it some day, but for now, this disables all validation.

Disabling Certificate Validation on Apache HttpClient Libraries.

The Apache HTTP client libraries do not use the same mechanism as the JVM provides, but they have the same annoying default behavior of throwing an exception if the certificate is missing or invalid.

package org.workcast.streams;

import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
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.conn.ClientConnectionManager;
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.impl.client.DefaultHttpClient;
import org.workcast.streams.SSLPatch;

/**
* Copyright Keith D Swenson, Creative Commons Share-Alike
* Feel free to use this code where you like, but keep this copyright
* statement in it, and understand that there is no guarantee to be
* suitable for any purpose, so read it carefully.
*/
public class HttpClientHelper {

    public static HttpClient wrapClient(HttpClient initial) throws Exception {
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, new TrustManager[]{SSLPatch.getDummyTrustManager()}, null);
        SSLSocketFactory ssf = new SSLSocketFactory(sc);
        ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        ClientConnectionManager ccm = initial.getConnectionManager();
        SchemeRegistry sr = ccm.getSchemeRegistry();
        sr.register(new Scheme("https", ssf, 443));
        return new DefaultHttpClient(ccm, initial.getParams());
    }

}

I could not find any way to changing the default, but instead after you create a HttpClient you have to pass it to this to construct one that will allow connections to self-signed servers.

Configuring OpenID4Java to Work on Level B

I have been using the OpenID4Java library to authenticate users of my systems.  Again, you might think that in the important world of authentication, you would want complete assurance of the trust of the site you are connecting to. The fact is however that many OpenID sites do not use SSL at all.  We know that getting to Level C can be expensive.  Level B woudl still be better than Level A in all situations, and so with this in mind I figured out how to disable certificate verification on OpenID4Java.

package org.workcast.streams;

import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.InMemoryConsumerAssociationStore;
import org.openid4java.consumer.InMemoryNonceVerifier;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.Discovery;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.html.HtmlResolver;
import org.openid4java.discovery.yadis.YadisResolver;
import org.openid4java.message.AuthRequest;
import org.openid4java.server.RealmVerifierFactory;
import org.openid4java.util.HttpFetcher;
import org.openid4java.util.HttpFetcherFactory;
import org.openid4java.util.HttpRequestOptions;
import org.openid4java.util.HttpResponse;

/**
* Copyright Keith D Swenson, Creative Commons Share-Alike
* Feel free to use this code where you like, but keep this copyright
* statement in it, and understand that there is no guarantee to be
* suitable for any purpose, so read it carefully.
*/
public class OpenIdHelper {

    /**
    * Constructs a new openID4Java Consumer Manager object, properly
    * initialized so that it does not validate certificates.
    */
    public static ConsumerManager newConsumerManager() throws Exception {
        // Install the all-trusting trust manager SSL Context
        SSLContext sc = SSLPatch.disableSSLCertValidation();

        HttpFetcherFactory hff = new HttpFetcherFactory(sc,
              SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        YadisResolver yr = new YadisResolver(hff);
        RealmVerifierFactory rvf = new RealmVerifierFactory(yr);
        Discovery d = new Discovery(new HtmlResolver(hff),yr,
              Discovery.getXriResolver());

        ConsumerManager manager = new ConsumerManager(rvf, d, hff);
        manager.setAssociations(new InMemoryConsumerAssociationStore());
        manager.setNonceVerifier(new InMemoryNonceVerifier(5000));
        return manager;
    }
}

With OpenID4Java, the trick is to get a properly initialized ConsumerManager that will not throw an exception when you attempt to connect to a self-signed server.  This is done with the same SSLContext that was created for the JVM, but in OpenID4Java you must explicitly pass this in as the constructor of the various objects.

Advertisements
 
5 Comments

Posted by on October 16, 2011 in Example Code

 

Tags: , ,

5 responses to “Working Around Java’s SSL Limitations

  1. kswenson

    October 16, 2011 at 10:35 pm

    Instead of this fix, the dummy trust manager should call the real trust manager. It should then save the result, and allow the calling program to retrieve this result. It should not throw an exception in any case. The calling program can then display to the user the status of the certificate, and take actions (such as rejecting the connection) if it wishes.

    If anyone has an implementation like that, please make a comment with a link to it.

    What would make a lot of sense would be that a connection over SSL to a self-signed server should be handled exactly as if it was a non-SSL regular socket, only it is better because it is encrypted. But again, a TrustManager that works that way would allow for any of these behaviors.

     
  2. Jay

    January 11, 2016 at 12:26 am

    This is very dangerous! Do not ever do this in production. Ever. Period. By disabling verification you will completely destroy any security of TLS.

    Furthermore you can use self signed certificate in java applications, or if the situation is more complex create a first self-signed certificate that acts as a CA first and then base your certs on it. Then put it in the truststore. Disabling verification is just an excuse for being lazy a it is dangerous.

     
    • kswenson

      January 11, 2016 at 10:37 am

      Oh good, we have a self styled security “expert” with exactly the kind of brain-dead knee jerk reactions that got us into this problem in the first place. What a great example of fear mongering with the “completely destroy any security” statement. I am leaving Jay’s comment in here because it is a good example of the kind of mentality you are up against that causes the problem in the first place.

      It makes me wonder: did Jay even read the article at all. If he did read it, he clearly didn’t understand it. He says “furthermore you can use self signed”… How could it be that he failed to notice that the entire article is about using a self-signed certificate. This Java library prevents you from being able to use a self signed web site.

      Perhaps Jay does not understand the concept of a public server. That is, a server which is provided for anyone to use without special configuration. Just point your program at it, and you can read the information. Unless he has been living in a cave for the last 20 years, he should have noticed that this is how browsers work: you point them at the server, and they retrieve pages. If you point them at a self-signed site, they retrieve the information, after giving a pointless warning. The Java library, however, completely refuses to act in the way that browsers do. Your ONLY way to get the Java library to act the way that a common browser does is to make use of my code above. If you are a system programmer who has tried to use this Java library in this situation you know that.

      His second paragraph seems to pertain to “private” servers. That is, I can put the server’s certificate into my client’s trusted store. Can anyone spot the problem with this? If I have a server that I want 100,000 people to be able to access (say, a small city) I have to go to all 100,000 people and tell them to “please install this special certificate before you can access the server”. If I set up a second server, I have to do that again. If I set up 100 servers, I have to go back to each 100,000 potential viewers and tell them to install these 100 certificates. This is all BEFORE they get any benefit from the server. If the service I am providing is big and profitable — like Amazon or the New York Post — I pay $500 (for each server every year) from a certificate from a signing authority and that solves the problem. But, if I am a non-profit charity, or a school, and the information being provided is distributed for the public good by an organization that is very cash strapped, every dollar spent on this is stolen from the people you are trying to help.

      The idea that this “completely destroys the security” represents the mentality that I am struggling with. The idea that all security is needed for every situation, and specifically that security is black-or-white: it is foolishness to insist that either “you do everything or you do nothing.” This is not about laziness, but cost, and nowhere does his arguments acknowledge the role that cost plays in this.

      The only thing I can add, is that since I wrote this article in 2011, there have appeared some good, free, signing authorities. “Let’s Encrypt” is a new Certificate Authority: It’s free, automated, and open. In Public Beta. I would agree, that if you can get a free certificate that works, then it would be better to put something like that on the system: no need to work around this Java limitation. That option unavailable when I wrote this might make all this unnecessary.

       

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: