java - 使用 Java8 的 SNI 客户端之谜

标签 java ssl sni

我有一个 Apache 网络服务器,它运行多个具有不同证书和 SNI 的 TLS 虚拟主机。

我可以使用 curl 访问各种虚拟主机(大概是 SNI 使它工作)。我也可以使用一个基本上只是 URL 上的 openConnection() 的小命令行 Java 程序来访问它们。

在我的 Tomcat 应用程序中,基本相同的客户端代码访问与客户端相同的 Apache 服务器,但总是以默认证书 (defaulthost.defaultdomain) 而不是在它尝试访问的 URL。 (这会产生一个 SunCertPathBuilderException——基本上它无法验证证书的证书路径,这当然是正确的,因为它是一个非官方证书。但是无论如何都不应该使用默认证书。)

就好像 SNI 在我的应用程序/Tomcat 的客户端被停用了一样。我不知道为什么它在我的应用程序和命令行之间的行为会有所不同;相同的 JDK,相同的主机等。

我找到了属性 jsse.enableSNIExtension,但我确认在这两种情况下它都设置为 true。问题:

  1. 有什么想法,甚至是疯狂的想法,为什么这两个程序的行为不同?

  2. 有什么办法可以调试吗?

这是 86_64 上的 Arch Linux,JDK 8u77,Tomcat 8.0.32。

最佳答案

这个答案来晚了,但我们刚刚遇到了问题(我不敢相信,这似乎是一个非常大的错误)。

它所说的一切似乎都是真的,但它不是默认的 HostnameVerifier 的罪魁祸首,而是故障排除者。当 HttpsClient 做 afterConnect 时首先尝试建立 setHost(仅当 socket 为 SSLSocketImpl 时):

SSLSocketFactory factory = sslSocketFactory;
try {
    if (!(serverSocket instanceof SSLSocket)) {
        s = (SSLSocket)factory.createSocket(serverSocket,
                                            host, port, true);
    } else {
        s = (SSLSocket)serverSocket;
        if (s instanceof SSLSocketImpl) {
            ((SSLSocketImpl)s).setHost(host);
        }
    }
} catch (IOException ex) {
    // If we fail to connect through the tunnel, try it
    // locally, as a last resort.  If this doesn't work,
    // throw the original exception.
    try {
        s = (SSLSocket)factory.createSocket(host, port);
    } catch (IOException ignored) {
        throw ex;
    }
}

如果您使用自定义 SSLSocketFactory 而不覆盖 createSocket()(没有参数的方法),则使用参数化良好的 createSocket 并且一切都按预期工作(使用客户端 sni 扩展)。但是当使用第二种方式(尝试 setHost en SSLSocketImpl)时,执行的代码是:

// ONLY used by HttpsClient to setup the URI specified hostname
//
// Please NOTE that this method MUST be called before calling to
// SSLSocket.setSSLParameters(). Otherwise, the {@code host} parameter
// may override SNIHostName in the customized server name indication.
synchronized public void setHost(String host) {
    this.host = host;
    this.serverNames =
        Utilities.addToSNIServerNameList(this.serverNames, this.host);
}

评论说明了一切。您需要在客户端握手之前调用 setSSLParameters。如果您使用默认的 HostnameVerifier,HttpsClient 将调用 setSSLParameters。但是没有相反方式的setSSLParameters执行。对于 Oracle,修复应该非常容易:

SSLParameters paramaters = s.getSSLParameters();
if (isDefaultHostnameVerifier) {
    // If the HNV is the default from HttpsURLConnection, we
    // will do the spoof checks in SSLSocket.
    paramaters.setEndpointIdentificationAlgorithm("HTTPS");

    needToCheckSpoofing = false;
}
s.setSSLParameters(paramaters);

Java 9 在 SNI 中按预期工作。但他们 (Oracle) 似乎不想解决这个问题:

关于java - 使用 Java8 的 SNI 客户端之谜,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36323704/

相关文章:

java - 不同任务的ThreadPoolExecutor

java - 继承java,我该怎么办?

amazon-web-services - 使用 google 域的 AWS SSL 配置

python - 使用 pyOpenSSL 处理 SNI - Python

Apache 2.4 将 SSL 限制到特定的子域虚拟主机

java - JMockit:单例类,测试顺序

java - WeakReference#get() 什么时候开始返回 null?

ssl - EKS - Envoy 动态转发代理 CA 验证错误

ssl - 使用 Erlang 无需验证即可获取客户端证书

java - SSL 协议(protocol)异常 : handshake alert: unrecognized_name: client-side code workaround?