为啥 HttpsURLConnection.get Server Certificates() 在 Java 6 和 Java7 中返回不同的结果?

Posted

技术标签:

【中文标题】为啥 HttpsURLConnection.get Server Certificates() 在 Java 6 和 Java7 中返回不同的结果?【英文标题】:Why is HttpsURLConnection.getServerCertificates() returning different results in Java6 vs Java7?为什么 HttpsURLConnection.get Server Certificates() 在 Java 6 和 Java7 中返回不同的结果? 【发布时间】:2012-11-21 23:42:14 【问题描述】:

我有这样的代码:

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

针对特定网站运行此代码,在 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

在 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

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

在 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.

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


编辑:a Python 2.7.1 script 返回与 Java6 相同的证书。 SSLConnection.get_peer_cert() 向我显示 CN 不匹配的证书。

【问题讨论】:

【参考方案1】:

我怀疑这是由于 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 Client 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 地址)。

【讨论】:

以上是关于为啥 HttpsURLConnection.get Server Certificates() 在 Java 6 和 Java7 中返回不同的结果?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 glTranslatef?为啥不直接更改渲染坐标?

为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?

为啥需要softmax函数?为啥不简单归一化?

为啥 g++ 需要 libstdc++.a?为啥不是默认值?

为啥或为啥不在 C++ 中使用 memset? [关闭]

为啥临时变量需要更改数组元素以及为啥需要在最后取消设置?