使用Java8的SNI客户端之谜 [英] SNI client-side mystery using Java8

查看:479
本文介绍了使用Java8的SNI客户端之谜的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我可以使用curl很好地访问各种虚拟主机(可能是SNI使这行得通)。我也可以使用一个基本上只是在URL上的openConnection()的命令行Java程序来访问它们。



在我的Tomcat应用程序中,基本相同的客户端 - 边代码访问与客户端相同的Apache服务器,但始终以默认证书(defaulthost.defaultdomain)结束,而不是在其尝试访问的URL中指定的虚拟主机的证书。 (这会产生一个SunCertPathBuilderException - 基本上它无法验证证书的证书路径,当然这是真的,因为它是非官方证书。但是不管怎么说都不应该使用默认证书。)



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



我找到了属性 jsse.enableSNIExtension ,但我确认它已设置为两种情况都是如此。问题:


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


  2. 我想如何调试这个?


这是Arch Linux on 86_64 ,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){
//如果我们无法通过隧道连接,请在本地尝试
//作为最后的手段。如果这不起作用,
//抛出原始异常。
try {
s =(SSLSocket)factory.createSocket(host,port);
} catch(IOException被忽略){
throw ex;
}
}

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

  //仅由HttpsClient用于设置指定的URI主机名
//
//请注意,在调用
// SSLSocket.setSSLParameters()之前必须调用此方法。否则,{@code host}参数
//可能会覆盖自定义服务器名称指示中的SNIHostName。
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){
//如果HNV是HttpsURLConnection的默认值,我们
//将在SSLSocket中进行欺骗检查。
paramaters.setEndpointIdentificationAlgorithm(HTTPS);

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

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




I have an Apache web server that runs several TLS virtualhosts with different certs and SNI.

I can access the various virtual hosts just fine using curl (presumably SNI makes it work). I can also access them fine with a little command-line Java program that basically just openConnection()s on a URL.

In my Tomcat application, the basic same client-side code accesses the same Apache server as a client, but always ends up with the default cert (defaulthost.defaultdomain) instead of the cert of the virtual host that was specified in the URL that it attempts to access. (This produces a SunCertPathBuilderException -- basically it can't verify the certificate path to the cert, which of course is true as it is a non-official cert. But then the default cert should not be used anyway.)

It's just as if SNI had been deactivated client-side in my application / Tomcat. I am at a loss why it should behave differently between my app and the command-line; same JDK, same host etc.

I found property jsse.enableSNIExtension, but I verified that it is set to true for both cases. Questions:

  1. Any ideas, even wild ones, why these two programs behave differently?

  2. Any ideas how I would debug this?

This is Arch Linux on 86_64, JDK 8u77, Tomcat 8.0.32.

解决方案

This answer comes late, but we just have hit the problem (I can't believe it, it seems a very big bug).

All what it said seems true, but it's not default HostnameVerifier the culprit but the troubleshooter. When HttpsClient do afterConnect first try to establish setHost (only when socket is 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;
    }
}

If you use a custom SSLSocketFactory without override createSocket() (the method without parameters), the createSocket well parametrized is used and all works as expected (with client sni extension). But when second way it's used (try to setHost en SSLSocketImpl) the code executed is:

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

The comments say all. You need to call setSSLParameters before client handshake. If you use default HostnameVerifier, HttpsClient will call setSSLParameters. But there is no setSSLParameters execution in the opposite way. The fix should be very easy for 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 is working as expected in SNI. But they (Oracle) seem not to want fix this:

这篇关于使用Java8的SNI客户端之谜的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆