为什么HttpsURLConnection.getServerCertificates()在Java6和Java7中返回不同的结果?

Che*_*eso 5 python java ssl httpsurlconnection

我有这样的代码:

    // 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());
        }
    }
Run Code Online (Sandbox Code Playgroud)

在Java 6上针对特定网站运行此代码,我得到的证书与Java 7不同.假设主机名为abc.myhost.com.

在Java6上,我得到:

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
Run Code Online (Sandbox Code Playgroud)

在Java7上,我得到:

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
Run Code Online (Sandbox Code Playgroud)

如果我打印出有效日期,那些也是不同的.和序列号一样.这些是不同的证书.

在Java 7上它看起来是正确的; 在Java 6上,我在主机名和CN之间存在分歧.证书看起来不对.

该服务器完全可能在代理之后.该服务器的所有者(我正在处理的项目中的合作伙伴)最近也可能更改了证书.可能有2个证书,一个在代理服务器上,另一个在代理服务器后面的服务器上.我让他们调查这个.

我的问题是,为什么我不能在Java7上获得与Java6相同的结果?Java改变了什么HttpsURLConnection.getServerCertificates()


对于好奇,这只是一个诊断工作.真正的错误是:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: 
    No subject alternative DNS name matching abc.myhost.com found.
Run Code Online (Sandbox Code Playgroud)

在这种情况下的问题通常是证书中主机名和CN之间的分歧.我已经验证了分歧,但仅限于Java 6.我想了解为什么Java6和Java7不同.


编辑: Python 2.7.1脚本返回与Java6相同的证书. SSLConnection.get_peer_cert()向我展示了CN不匹配的证书.

Bru*_*uno 10

我怀疑这是由于Java 7中客户端引入的服务器名称指示支持.

SNI允许客户端在SSL/TLS初始请求中指定主机名,特别是能够在具有不同证书的相同IP地址/端口上托管多个主机名(Apache Httpd称为基于名称的虚拟主机).在SSL/TLS握手期间了解所请求的主机名允许服务器在使用任何HTTP流量之前服务于正确的证书(HTTP Host标头在HTTP级别使用,但对于HTTPS来说为时已晚).

当客户端不支持它时,服务器不知道客户端确实需要哪个主机名,并且通常会回退到默认主机值并提供默认证书.

(请注意,在Win XP和某些移动浏览器上,任何版本的IE都会遇到同样的问题.)

编辑:关注你的编辑(URL url = new URL(urlString); // https://abc.myhost.com).

这似乎证实了SNI问题.(您可以使用Wireshark检查TLS客户端Hello消息中是否有服务器名称扩展名.)

使用Java 7和任何支持SNI的客户端,在请求时https://abc.myhost.com,您确实会获得一个有效的证书abc.myhost.com(假设服务器配置正确),因为HttpsURLConnection它还告诉JSSE(Java SSL/TLS堆栈)使用服务器名称扩展和使用该URL的主机名启动SSL/TLS连接.

对于Java 6和任何不支持SNI的客户端(Python 2.7,至少没有其他库),您将获得服务器在连接到该IP地址和端口时默认提供的证书.

它与HttpsURLConnection.getServerCertificates()或无关SSLConnection.get_peer_cert().相反,这是因为服务器期望客户端支持SNI,而某些较旧的客户端/平台则不支持.

如果您需要在Windows XP上支持Java 6,Python 2.x,Internet Explorer(或使用默认MS API的其他客户端),您将无法使用SNI.在这种情况下,您应该联系服务器管理员以更改配置,而不是使用SNI(如果需要这些多个主机,则可能需要额外的IP地址).