TLS扩展“服务器名称指示”(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 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(); } } 

无论出于何种原因,Java SNI实现显然不提供服务器端的实际主机名。 相反,文档[1](在“基于SSLSocket的虚拟服务器调度程序”下)建议手动解析初始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。

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

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

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

我会尝试实现自己的SNIMatcher(扩展抽象的)并在访问mymatcher.matches(…)方法时拦截并保存sniservername(另一个抽象)。

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

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