TLS扩展“服务器名称指示”(SNI):服务器端不可用的值

时间:2014-01-02 16:42:50

标签: java ssl java-8 sni

基于JSSE示例,我试图在服务器端获取TLS参数“服务器名称指示”(SNI)的值 - 但没有成功。我确信该值是由客户端发送的,因为我使用了显示该值的网络嗅探器(Wireshark)。

但是当我使用下面的代码片段时,服务器名称参数列表为空(显示“协议”):

public void connectionAccepted(Socket socket) 
{ 
  System.out.println("Connection accepted!"); 
  try { 
    /* get SNI parameter */ 
    SSLSocket sslSocket = (SSLSocket)socket; 
    SSLParameters sslParams = sslSocket.getSSLParameters(); 

    List<SNIServerName> serverNames = sslParams.getServerNames(); 
    for(SNIServerName item : serverNames){ 
      System.out.println("SNI: " + item.toString()); 
    } 

    String[] protocols = sslParams.getProtocols(); 
    for(String item : protocols) { 
      System.out.println("Protocols: " + item.toString()); 
    } 
  } catch (Exception e) { 
    e.printStackTrace(); 
  } 
} 

3 个答案:

答案 0 :(得分:1)

无论出于何种原因,Java SNI实现显然不提供服务器端的实际主机名。相反,文档[1](在&#34;基于SSLSocket&#34的虚拟服务器调度程序;)建议手动解析初始SSL数据包并从那里提取服务器名称。

您还可以使用SNIMatcher [2]来要求客户端提供某个名称;如果匹配器不匹配,则客户端在SSL层发生错误。

[1] http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SNIExamples [2] http://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SNIMatcher.html

更新:我编写了一个自定义SNI解析器,它的工作原理如下:客户端将SNI作为SSL ClientHello消息的一部分发送,这是第一个作为一部分发送的消息设置SSL连接。我的实现首先解析它,然后使用与请求的主机名匹配的正确服务器端证书设置SSLEngine。

答案 1 :(得分:1)

该功能(非常难以理解)不具备SSLSocket的开箱即用功能(也不适用于较新的SSLEngine)。

我自己遇到了同样的问题,最后编写了一个开源库:TLS Channel。该库的范围实际上更大:它是SSLEngine的完全抽象(它很难直接使用),将其暴露为ByteChannel。

关于SNI,库在创建SSLEngine之前解析第一个字节。然后,用户可以向服务器通道提供功能,根据收到的域名选择SSLContexts。

答案 2 :(得分:0)

我要尝试的是实现我自己的SNIMatcher(扩展抽象的)和拦截&amp;在访问mymatcher.matches(...)方法时保存sniservername(另一个摘要)。

如果是snihostname的sniservername实例,则为snihostname.getAsciiName()。否则,你将不得不挖掘如何阅读sniservername.getEncoded()byte []。

一旦退出握手和连接,我会检查我的snimatcher上保留的主机名。当你手头有sniservername对象时,你可能必须输出字符串主机名,因为,谁知道(你会!),它可能在snimatcher.matches(...)调用之后立即变为无效。