Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is HttpsURLConnection.getServerCertificates() returning different results in Java6 vs Java7?

I have code like this:

    // 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(urlString); // https://abc.myhost.com
    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
    conn.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                System.out.println("verify:" + arg0);
                return true;
            }
        });

    System.out.println("HTTP status: " + conn.getResponseCode());
    Certificate[] certs = conn.getServerCertificates();
    int c=0;
    for (Certificate cert : certs){
        String t = cert.getType();
        System.out.println(String.format("\ncert[%d]: %s",c,t));
        c++;
        if (pi.verbose) {
            System.out.println(cert);
        }
        else if (cert instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) cert;
            System.out.println(x509cert.getSubjectDN().getName());
        }
    }

Running this code against a particular website, on Java 6, I get a different cert than I get for Java 7. Let's say the hostname is abc.myhost.com.

on Java6 I get:

cert[0]: X.509
CN=example.com,OU=Secure Link SSL Pro,O=Company Name Here, 
    STREET=2001 Space Odyssey Dr,L=Weirton,ST=Wv,2.5.4.17=#13053330303034,C=US

on Java7 I get:

cert[0]: X.509
CN=abc.myhost.com,OU=Secure Link SSL Pro,O=Company Name Here, 
    STREET=2001 Space Odyssey Dr,L=Weirton,ST=Wv,2.5.4.17=#13053330303034,C=US

If I print out the valid dates, those are different too. As are the serial numbers. These are different certs.

On Java 7 it looks right; On Java 6 I have a disagreement between the hostname and the CN. The cert looks wrong.

It is entirely possible that this server is behind a proxy. It is also possible that the owner of that server (a partner in a project I'm working on) has changed the cert recently. It's possible that there are 2 certs, one on the proxy server and one on the server behind the proxy server. I have them looking into this.

The question I have is, why wouldn't I get the same results on Java7 as on Java6? Did Java change something in HttpsURLConnection.getServerCertificates()?


For the curious, this is just a diagnostic effort. The real error is:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: 
    No subject alternative DNS name matching abc.myhost.com found.

The problem in this case is usually a disagreement between the hostname and the CN in the cert. I've verified the disagreement, but only on Java 6. I'd like to understand why Java6 and Java7 are different.


EDIT: a Python 2.7.1 script returns the same cert as Java6. SSLConnection.get_peer_cert() shows me the cert with the mismatched CN.

like image 266
Cheeso Avatar asked Nov 21 '12 23:11

Cheeso


1 Answers

I would suspect this is due to Server Name Indication support introduced on the client side in Java 7.

SNI allows the client to specify the host name within the SSL/TLS initial request, in particular to be able to host multiple host names on the same IP address/port with distinct certificates (what Apache Httpd calls name-based virtual hosts). Knowing the requested host name during the SSL/TLS handshake allows the server to server the right certificate, before any HTTP traffic is used (the HTTP Host header is used at the HTTP level, but it's too late for HTTPS).

When the client doesn't support it, the server doesn't know which host name is really required by the client and will usually fall back onto a default host value and serve the default certificate.

(Note that you'll experience the same problem with any version of IE on Win XP and possibly some mobile browsers.)

EDIT: Following your edit (URL url = new URL(urlString); // https://abc.myhost.com).

This seems to confirm the SNI issue. (You can check using Wireshark to see if there's a server name extension in the TLS Client Hello message.)

With Java 7 and any client supporting SNI, when requesting https://abc.myhost.com, you will indeed get a certificate valid for abc.myhost.com (provided that the server is correctly configured), because HttpsURLConnection also tells the JSSE (the Java SSL/TLS stack) to use the server name extension and initiate the SSL/TLS connection with the host name for that URL.

With Java 6 and any client that doesn't support SNI (Python 2.7, at least without other libraries), you'll get the certificate that the server presents by default when connecting to that IP address and port.

It has nothing to do with HttpsURLConnection.getServerCertificates() or SSLConnection.get_peer_cert(). Rather, it's because the server expects the client to support SNI, which some older clients/platforms don't.

If you need to support Java 6, Python 2.x, Internet Explorer (or other clients that use the default MS API) on Windows XP, you won't be able to use SNI. In this case, you should contact the server administrator to change the configuration not to use SNI (which may require an additional IP address if these multiple hosts are required).

like image 63
Bruno Avatar answered Nov 14 '22 23:11

Bruno