基于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();
}
}
答案 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(...)调用之后立即变为无效。