Question

Based on the JSSE examples, I'm trying to get the value of the TLS parameter "Server Name Indication" (SNI) on the server side - without success. I'm sure that the value is sent by the client since I used a network sniffer (Wireshark) that shows the value.

But when I use the following code fragment, the list of server name parameters is empty (while the "protocols" are shown):

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(); 
  } 
} 
Was it helpful?

Solution

For whatever reason, the Java SNI implementation apparently does not provide the actual host name on the server side. Instead, the documentation [1] (under "Virtual Server Dispatcher Based on SSLSocket") recommends manually parsing the initial SSL packet and extracting the server name from there.

You could also use an SNIMatcher [2] to require that the client provides a certain name; if the matcher does not match, then the client gets an error at the SSL layer.

[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

Update: I've written a custom SNI parser and it works like this: the client sends the SNI as part of the SSL ClientHello message, which is the first message sent as part of setting up an SSL connection. My implementation first parses that, and then sets up an SSLEngine with the right server-side certificate matching the requested host name.

OTHER TIPS

That functionality is (quite inexplicable) not available out-of-the box with SSLSocket (nor with the newer SSLEngine).

I came across the same problem myself and ended up writing an open source library: TLS Channel. The scope of the library is actually bigger: it is a complete abstraction for SSLEngine (which is seriously hard to use directly), exposing it as a ByteChannel.

Regarding SNI, the library does the parsing of the first bytes before creating the SSLEngine. The user can then supply a function to the server channel, to select SSLContexts depending on the received domain name.

What I would try is to implement my own SNIMatcher (extends the abstract one) and intercept & save the sniservername (another abstract) as they visit mymatcher.matches(...) method.

If the sniservername instanceof snihostname, they snihostname.getAsciiName(). otherwise, you will have to dig how to read the sniservername.getEncoded() byte[].

Once out of the handshake and connection, I would inspect the retained hostname on my snimatcher. You may have to get the string hostname out while you have the sniservername object in hand, because, who knows (you will!), it may become invalid right after the snimatcher.matches(...) call.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top