11

I have to call an HTTP service hosted on web server with an invalid SSL certificate. In dev, I'm importing the certificate with keytool but the certificate will be different on each client install, so I can't just bundle it.

Foreword: I DO know that skipping SSL validation is really ugly. In this specific case, I would not even need SSL and all other communications in the system are over simple HTTP. So I really don't care about MITM attacks or such. An attacker would not need to go as far as to break SSL because there is no SSL for the data. This is support for a legacy system over which I have no control.

I'm using HttpURLConnection with an SSLSocketFactory that has a NaiveTrustManager and a NaiveHostnameVerifier. This works on some self-signed servers I tried but not on the customer's site. The error I'm getting is:

javax.net.ssl.SSLKeyException: [Security:090477]Certificate chain received from xxxxxxxxxx was not trusted causing SSL handshake failure.
    at com.certicom.tls.interfaceimpl.TLSConnectionImpl.fireException(Unknown Source)
    at com.certicom.tls.interfaceimpl.TLSConnectionImpl.fireAlertSent(Unknown Source)
    at com.certicom.tls.record.handshake.HandshakeHandler.fireAlert(Unknown Source)
    at com.certicom.tls.record.handshake.HandshakeHandler.fireAlert(Unknown Source)
    at com.certicom.tls.record.handshake.ClientStateReceivedServerHello.handle(Unknown Source)
    at com.certicom.tls.record.handshake.HandshakeHandler.handleHandshakeMessage(Unknown Source)
    at com.certicom.tls.record.handshake.HandshakeHandler.handleHandshakeMessages(Unknown Source)
    at com.certicom.tls.record.MessageInterpreter.interpretContent(Unknown Source)
    at com.certicom.tls.record.MessageInterpreter.decryptMessage(Unknown Source)
    at com.certicom.tls.record.ReadHandler.processRecord(Unknown Source)
    at com.certicom.tls.record.ReadHandler.readRecord(Unknown Source)
    at com.certicom.tls.record.ReadHandler.readUntilHandshakeComplete(Unknown Source)
    at com.certicom.tls.interfaceimpl.TLSConnectionImpl.completeHandshake(Unknown Source)
    at com.certicom.tls.record.WriteHandler.write(Unknown Source)
    at com.certicom.io.OutputSSLIOStreamWrapper.write(Unknown Source)
    at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
    at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
    at java.io.FilterOutputStream.flush(FilterOutputStream.java:123)
    at weblogic.net.http.HttpURLConnection.writeRequests(HttpURLConnection.java:154)
    at weblogic.net.http.HttpURLConnection.getInputStream(HttpURLConnection.java:358)
    at weblogic.net.http.SOAPHttpsURLConnection.getInputStream(SOAPHttpsURLConnection.java:37)
    at weblogic.net.http.HttpURLConnection.getResponseCode(HttpURLConnection.java:947)
    at (my own code)

My SimpleSocketFactory looks like:

public static final SSLSocketFactory getSocketFactory()
{
    if ( sslSocketFactory == null ) {
        try {
            // get ssl context
            SSLContext sc = SSLContext.getInstance("SSL");

            // Create a trust manager that does not validate certificate chains
            TrustManager[] trustAllCerts = new TrustManager[]{
                new NaiveTrustManager() {
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        log.debug("getAcceptedIssuers");
                        return new java.security.cert.X509Certificate[0];
                    }
                    public void checkClientTrusted(
                        java.security.cert.X509Certificate[] certs, String authType) {
                        log.debug("checkClientTrusted");
                    }
                    public void checkServerTrusted(
                        java.security.cert.X509Certificate[] certs, String authType) {
                        log.debug("checkServerTrusted");
                    }
                }
            };

            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            // EDIT: fixed the following line that was redeclaring SSLSocketFactory sslSocketFactory, returning null every time. Same result though.
            sslSocketFactory = sc.getSocketFactory();

            HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
            // EDIT: The following line has no effect
            //HttpsURLConnection.setDefaultHostnameVerifier(new NaiveHostNameVerifier());

        } catch (KeyManagementException e) {
            log.error ("No SSL algorithm support: " + e.getMessage(), e);
        } catch (NoSuchAlgorithmException e) {
            log.error ("Exception when setting up the Naive key management.", e);
        }
    }
    return sslSocketFactory;
}

The NaiveHostnameVerifier has a way to limit the valid hosts but it's left null, so basically accepting anything:

public class NaiveHostnameVerifier implements HostnameVerifier {
    String[] patterns;

    public NaiveHostnameVerifier () {
        this.patterns=null;
    }

    public NaiveHostnameVerifier (String[] patterns) {
        this.patterns = patterns;
    }

    public boolean verify(String urlHostName,SSLSession session) {
        if (patterns==null || patterns.length==0) {
            return true;
        } else {
            for (String pattern : patterns) {
                if (urlHostName.matches(pattern)) {
                    return true;
                }
            }
            return false;
        }
    }
}

The usage is like this:

    try {
        conn = (HttpURLConnection)url.openConnection();
        if (conn instanceof HttpsURLConnection) {
                ((HttpsURLConnection)conn).setSSLSocketFactory(SimpleSSLSocketFactory.getSocketFactory());
                // EDIT: added this line, the HV has to be set on connection, not on the factory.
                ((HttpsURLConnection)conn).setHostnameVerifier(new NaiveHostnameVerifier());
        }
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-type","application/x-www-form-urlencoded");
        conn.connect();

        StringBuffer sbContent = new StringBuffer();
        // (snip)
        DataOutputStream stream = new DataOutputStream(conn.getOutputStream ());
        stream.writeBytes(sbContent.toString());
        stream.flush();
        stream.close();
    } catch (ClassCastException e) {
        log.error("The URL does not seem to point to a HTTP connection");
        return null;
    } catch (IOException e) {
        log.error("Error accessing the requested URL", e);
        return null;
    }

When I'm searching on the error message, most people just import the certificate in their store but again, I can't really do that because I don't know which certificate it'll be. My only alternative if this doesn't work is to make a tool that can download the certificate and add it in an easier way that cryptic command lines but I'd rather let my Java code just ignore the invalid certificate.

Any idea ?

Eric Darchis
  • 24,537
  • 4
  • 28
  • 49
  • 4
    Do you realise that ignoring certificate (and host name) verification opens the connections to potential MITM attacks? There seems to be a flaw in your (administrative) security procedures if you have to ignore certificate errors. In addition, you should look at the Certicom TLS options, since you're visibly not using the default JSSE provider. – Bruno Nov 20 '12 at 13:17
  • I am well aware of MITM attacks. In fact, if somebody is able to perform a MITM attack on that network, we have much bigger problems than him intercepting the communication. At first, I was considering a step in the software where we retrieve the certificate and if validated by the user, store it for further use. This is however a bigger challenge than the small problem I'm trying to address. I'll have a look at Certicom TLS options. – Eric Darchis Nov 20 '12 at 18:14
  • 2
    WebLogic has done horrible, horrible things with SSL, and I pity you for having to try to deal with it. They don't follow Java standards have have their own screwed up and broken way of messing everything up. – erickson Nov 20 '12 at 20:08
  • 1
    You're sufficiently concerned that there may be someone in a position to eavesdrop, but you're also assuming that they won't be able to alter the traffic (MITM). If you're worried about the former, I'm not sure I'd make this assumption about the latter. (If you really just want to protect against eavesdropping, but not MITM, it might be better to use anonymous cipher suites instead, without certificate.) – Bruno Nov 22 '12 at 16:57
  • @Bruno you misunderstood me. I don't care about eavesdropping. At all. There are much more important communications going on unencrypted on the same network. So if some one is able to place a MITM there, he won't even need to go that far to get the juicy bits of data. That's why I don't care about this SSL certificate. – Eric Darchis Nov 26 '12 at 11:13
  • So why use SSL at all? Why not use plaintext? Via an HTTP: URL? – user207421 Oct 24 '13 at 21:41
  • @EJP I was accessing a legacy system that imposed HTTPS on some operations and HTTP on others. The actual data was in HTTP. Since then, we ported that app to JBoss and at install-time, we ask the admin to validate the certificate and then only accept this one. On Weblogic, we try to generate a trustStore and let the sysadmin manually configures/import/adapt his stores. Inconvenient but no choice. – Eric Darchis Oct 27 '13 at 19:07

2 Answers2

3

There is in fact nothing wrong with the code above. The problem seems to lie with Weblogic and this Certicom TLS module. When I look at the server options, SSL and Advanced I see that I can specify a custom HostnameVerifier (SSLMBean.HostnameVerifier) but the only element suggesting the ability to interfere with Certificate validation is deprecated.

I tried the above code outside of Weblogic and it worked beautifully (fixed the HostnameVerifier in the post though).

Then I tried to add "-DUseSunHttpHandler=true" to the Weblogic parameters as suggested by ipolevoy in this other question. It started working.

That being said, switching the HTTP handler on an Oracle Service Bus server seems a bit risky. There might well be side-effects that come back to bite me in a few weeks time...

I also attempted to define my own trustStore and point it to a jssecacert that contained the required key. It was also ignored by Weblogic because it has its own setting of the trustStore for each server. So I'm resorting to ask the administrator to manually import the required keys or point Weblogic to my own store.

Community
  • 1
  • 1
Eric Darchis
  • 24,537
  • 4
  • 28
  • 49
  • By the way, I just discovered the HostnameVerifier could not be customized either and is failing with "javax.net.ssl.SSLKeyException: [Security:090504]Certificate chain received from intranet.company.com - x.x.x.x failed hostname verification check. Certificate contained *.company.com but check expected intranet.company.com". Weblogic, I hate you. I really do. – Eric Darchis Nov 26 '12 at 11:19
0

Actually, this is a know bug in Weblogic versions below 10.3.5, for which there is a patch available from Oracle. Please see document 1474989.1 in My Oracle Support for details.

The fix above is a non-recommended (but supported) workaround by Oracle, which will work, but is not the preferred solution.

The preferred solution is to download the patch mentioned in the Oracle article, and replace the SSL hostname verifier with the new one which is also part of Weblogic 10.3.5 and above. If you wish to remain compliant with Oracle in terms of support, this is the way to go.

Ajunne
  • 1
  • 1