Pregunta ¿Por qué HttpsURLConnection.getServerCertificates () devuelve resultados diferentes en Java6 vs Java7?


Tengo un código como este:

    // 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());
        }
    }

Al ejecutar este código en un sitio web en particular, en Java 6, obtengo un certificado diferente del que obtengo para Java 7. Digamos que el nombre de host es abc.myhost.com.

en Java6 me sale:

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

en Java7 me sale:

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

Si imprimo las fechas válidas, esas también son diferentes. Como son los números de serie. Estos son diferentes certificados.

En Java 7 se ve bien; En Java 6 tengo un desacuerdo entre el nombre de host y el CN. El certificado se ve mal.

Es completamente posible que este servidor esté detrás de un proxy. También es posible que el propietario de ese servidor (un socio en un proyecto en el que estoy trabajando) haya cambiado el certificado recientemente. Es posible que haya dos certs, uno en el servidor proxy y otro en el servidor detrás del servidor proxy. Los tengo investigando esto.

La pregunta que tengo es: ¿por qué no obtendría los mismos resultados en Java7 que en Java6? ¿Java cambió algo en HttpsURLConnection.getServerCertificates()?


Para los curiosos, esto es solo un esfuerzo de diagnóstico. El verdadero error es:

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

El problema en este caso suele ser un desacuerdo entre el nombre de host y el CN ​​en el certificado. He verificado el desacuerdo, pero solo en Java 6. Me gustaría entender por qué Java6 y Java7 son diferentes.


EDITAR: un script de Python 2.7.1 devuelve el mismo cert como Java6. SSLConnection.get_peer_cert() me muestra el certificado con el CN ​​no coincidente.


5
2017-11-21 23:42


origen


Respuestas:


Sospecho que esto se debe a la compatibilidad con la Indicación del nombre del servidor introducida en el lado del cliente en Java 7.

SNI permite al cliente especificar el nombre de host dentro de la solicitud inicial de SSL / TLS, en particular para poder hospedar múltiples nombres de host en la misma dirección IP / puerto con certificados distintos (lo que Apache Httpd llama hosts virtuales basados ​​en nombre). Saber el nombre de host solicitado durante el protocolo de enlace SSL / TLS permite al servidor presentar el certificado correcto, antes de que se use el tráfico HTTP (el HTTP Host el encabezado se utiliza en el nivel HTTP, pero es demasiado tarde para HTTPS).

Cuando el cliente no lo admite, el servidor no sabe qué nombre de host es realmente requerido por el cliente y, por lo general, recurre a un valor de host predeterminado y entrega el certificado predeterminado.

(Tenga en cuenta que experimentará el mismo problema con cualquier versión de IE en Win XP y posiblemente algunos navegadores móviles).

EDITAR: Siguiendo tu edición (URL url = new URL(urlString); // https://abc.myhost.com)

Esto parece confirmar el problema de SNI. (Puede verificar usando Wireshark para ver si hay una extensión de nombre de servidor en el mensaje Hello del Cliente TLS).

Con Java 7 y cualquier cliente compatible con SNI, cuando solicite https://abc.myhost.com, de hecho obtendrá un certificado válido para abc.myhost.com (siempre que el servidor esté configurado correctamente), porque HttpsURLConnection También le dice a JSSE (la pila Java SSL / TLS) que use la extensión del nombre del servidor e inicie la conexión SSL / TLS con el nombre de host para esa URL.

Con Java 6 y cualquier cliente que no admita SNI (Python 2.7, al menos sin otras bibliotecas), obtendrá el certificado que el servidor presenta de forma predeterminada al conectarse a esa dirección IP y ese puerto.

No tiene nada que ver con HttpsURLConnection.getServerCertificates() o SSLConnection.get_peer_cert(). Más bien, es porque el servidor espera que el cliente admita SNI, lo que algunos clientes / plataformas más antiguos no tienen.

Si necesita soportar Java 6, Python 2.x, Internet Explorer (u otros clientes que usen la API MS predeterminada) en Windows XP, no podrá usar SNI. En este caso, debe ponerse en contacto con el administrador del servidor para cambiar la configuración y no usar SNI (que puede requerir una dirección IP adicional si estos múltiples hosts son necesarios).


10
2017-11-22 11:05