Bug 57108 - Implement multiple sslcontext SNI (server name indication) dispatch
Implement multiple sslcontext SNI (server name indication) dispatch
Status: RESOLVED FIXED
Product: Tomcat 9
Classification: Unclassified
Component: Connectors
unspecified
All All
: P2 enhancement with 5 votes (vote)
: -----
Assigned To: Tomcat Developers Mailing List
:
Depends on:
Blocks:
  Show dependency tree
 
Reported: 2014-10-17 18:48 UTC by quartz
Modified: 2015-04-29 21:40 UTC (History)
3 users (show)



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description quartz 2014-10-17 18:48:04 UTC
SSL/TLS defines a mechanism for virtual host names on the same ip/port to have distinct server certs (and protocols, and else), avoiding the use of wildcard certs.

With jdk8, a TLS server can prefetch the ClientHello's server name indication (SNI) extension from the tcp accepted socket inputstream, then create an sslsocket with the new SSLSocketFactory.createSocket(socket, inputstream, autoclose) method (the the inputstream arg is just the replay of the prefetched bytes).

Unfortunately, all socket factories come from a SSLContext already initialized on keymanagers/trustmanagers on key/trust stores. Once a sslsocket is accepted from an sslsocketfactory, the sslcontext/server cert is already chosen.

To implement this under tomcat, the SSL connectors must only accept tcp connetions, detect the intended hostname, choose the proper sslcontext and then forward to the sslsocketfactory to handshake correctly. This means defining more than one keystore/truststore/params on the unique ssl connector.

One way to express this potentially large configuration would be to point to a mapping file outside the server.xml, but it should be possible to inline this data section too (some new tag element under the connector, perhaps a collection).

Ultimately, the admin should be able to map a hostname to an sslcontext descriptor (which is more than just keystore/truststore, but also versions, protocols, etc...)

Backward compatibility is desirable, so the previous attributes would only be assimilated as a single mapping of all hostnames. In fact, these mapping will require a default when no hostname pattern matches, so it is probably good to plan for a collection of sslcontext mappings plus 1 default sslcontext.

The jdk8 docs are giving complete examples on the prefectching mecanism and how to use SNI.

http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SNIExamples

Particularly the section "Working with Virtual Infrastructures".
Comment 1 Mark Thomas 2014-10-17 19:03:21 UTC
Correct the component - this has nothing to do with the Connectors component which is the web server end of the AJP implementation.

Tomcat 8 has to run on a minimum of Java 7 so this is something on the roadmap for Tomcat 9. Leave this in Tomact 8 until work starts on Tomcat 9 and the product exists in Bugzilla.
Comment 2 Christopher Schultz 2014-10-17 19:50:24 UTC
This may be an opportunity to fix the inability to respond to HTTP requests on HTTPS endpoints.

We get complaints every once in a while that if you "telnet host 443" against Tomcat, you get a hung connection while with httpd, you get an "400 Bad Request" response that actually says "you are making an HTTP connection to an HTTPS server". Completely changing the infrastructure and handling of secure sockets isn't justified merely to scratch this itch, but supporting SNI is a much bigger motivating force. Scratching this itch simultaneously would be great.
Comment 4 Mark Thomas 2015-01-17 17:56:47 UTC
Move to Tomcat 9 now it exists.
Comment 5 Unlogic 2015-01-23 08:15:19 UTC
This will surely be a killer function if it makes to Tomcat 9. As the use of Windows XP is fading out the demand for SNI support is increasing by the day.
Comment 6 Christopher Schultz 2015-03-17 13:25:13 UTC
Proposed configuration vocabulary, which is backward-compatible with existing configurations:


   <Connector ...
      truststoreFile="..." (and other truststore attributes)
      keystoreFile="..." (and other keystore attributes)
      >
     <TLSAlias hostname="alternate.hostname"
         truststoreFile="..." (and other truststore attributes)
         keystoreFile="..." (and other keystore attributes)
         [other allowed configuration attributes]
         />
     <TLSAlias hostname="alternate.hostname"
         truststoreFile="..." (and other truststore attributes)
         keystoreFile="..." (and other keystore attributes)
         [other allowed configuration attributes]
         />
   </Connector>

The TLS configuration attributes on the <Connector> will become the default TLS configuration for a request for a hostname that does not match any of the <TLSAlias> elements' hostname fields. Any request that exactly matches a hostname (or, perhaps we can do prefixing, globbing and/or regular expressions if people want to do that kind of thing) will instead use the TLS configuration of its matching <TLSAlias> element.

There are some configuration elements that are appropriate to allow a <TLSAlias> element to override from the default. Proposed are all but those that appear in the following section.

There are some attributes that should probably not be overridable in the <TLSAlias> elements, due their effect on all connections. Proposed attributes:

  SSLProtocol

Care must be taken to ensure that subsequent handshakes -- for example, for the purposes of client re-negotiation or cipher-suite-switching -- do not allow a single client to switch from one hostname to another to, for instance, avoid some part of the authentication scheme or take advantage of a faulty configuration in host alias in order to "upgrade" to a different host with more stringent requirements.
Comment 7 Unlogic 2015-03-17 13:42:02 UTC
I think that sounds like a very good approach which would be easy to add to existing server configurations.

Since a single certificate can contain multiple subject alternative names (http://en.wikipedia.org/wiki/SubjectAltName) which may be very different from each other I would propose to use an approach similar to the way aliases are added to hosts.

   <Connector ...
      truststoreFile="..." (and other truststore attributes)
      keystoreFile="..." (and other keystore attributes)
      >
     <TLSAlias hostname="alternate.hostname"
         truststoreFile="..." (and other truststore attributes)
         keystoreFile="..." (and other keystore attributes)
         [other allowed configuration attributes]>

         <Alias>somehost.com</Alias>
         <Alias>anotherhost.com</Alias>
         <Alias>averydifferenthost.org</Alias>
    </TLSAlias>


     <TLSAlias hostname="alternate.hostname"
         truststoreFile="..." (and other truststore attributes)
         keystoreFile="..." (and other keystore attributes)
         [other allowed configuration attributes]
         />
   </Connector
Comment 8 Christopher Schultz 2015-03-17 15:56:31 UTC
That sounds reasonable to me. Since the configuration for each hostname would need to be maintained separately, being able to tie several hostnames together would be beneficial.

On the other hand, if regular expressions are used for hostnames, the complxity can be handled there instead of in a slightly larger look-up table with aliases, etc.

Honestly, even though I mentioned it as a possibility, I think that the use of regular expressions for hostname matching is probably more overhead than anyone wants to perform for every TLS request that comes through the door.
Comment 9 Christopher Schultz 2015-03-20 15:25:45 UTC
I'm starting to re-think the <Connector> configuration because it's starting to look a lot like the <Host> configuration.

Would it make more sense to put the TLS configuration on the <Host> element instead? This would be more in line with Apache httpd, for instance.

The connector would have to reach-into the <Host> configuration to pick-up all the configuration. I'm not sure if this is a good idea (or even possible), but from a human configuration perspective, I think that using the <Host> as the container for TLS configuration makes more sense than having it on the <Connector>.
Comment 10 Unlogic 2015-03-23 19:54:44 UTC
Well this is a bit tricky because there is two sides to this coin.

In some cases you have a wildcard certificates or subject alternative name certificates the cover lots of domains. In those cases the current connector based approach works fine fine.

But if you turn things around and have lets say 50 domains all with their own host and certificate and maybe even a few aliases for some hosts that in turn also require separate certificates. In that case the host based approach would make things simpler since you can put everything inside the hosts element.

A trade off between the two solutions could be to define the keystores using a separate element in the config like when you define a connection pool. And then make it possible for both the connectors, hosts and aliases to refer back to the defined keystores depending on the use case.

Here's an example:

   <Keystore
         name="firstKeystore"
         truststoreFile="..." (and other truststore attributes)
         keystoreFile="..." (and other keystore attributes)
         [other allowed configuration attributes]>

   <Keystore
         name="secondKeystore"
         truststoreFile="..." (and other truststore attributes)
         keystoreFile="..." (and other keystore attributes)
         [other allowed configuration attributes]>

   <Keystore 
         name="thirdKeystore"
         truststoreFile="..." (and other truststore attributes)
         keystoreFile="..." (and other keystore attributes)
         [other allowed configuration attributes]>

   <Connector ... /> (a generic https connector not bound to any particular keystore that instead looks up the keystore based on the host/alias)

   <Connector keystoreRef="firstKeystore" ... /> (a https connector bound to the specified keystore like current tomcat versions)

   <Host name="hostone.com"... /> (a host not bound to any particular keystore)

   <Host name="hosttwo.net" keystoreRef="secondKeystore" ... />

   <Host name="hostthree.net" keystoreRef="secondKeystore" ...>

       <Alias keystoreRef="thirdKeystore">foo.com</Alias>

       <Alias keystoreRef="firstKeystore">boo.com</Alias>

       <Alias>moo.com</Alias>

   </Host>

I hope that my example makes sense. It would make the keystore/certificate configuration a bit more flexible and support "both sides of the coin".
Comment 11 Christopher Schultz 2015-03-24 14:50:13 UTC
(In reply to Unlogic from comment #10)
> Well this is a bit tricky because there is two sides to this coin.
> 
> In some cases you have a wildcard certificates or subject alternative name
> certificates the cover lots of domains. In those cases the current connector
> based approach works fine fine.

We have to continue to support the current connector configuration, anyway. I figured that whatever configuration the <Connector> had would be the default for all of the hosts. In that case, you'd probably want to put the wildcard cert, etc. on the <Connector> and do nothing special for each host.

> A trade off between the two solutions could be to define the keystores using
> a separate element in the config like when you define a connection pool. And
> then make it possible for both the connectors, hosts and aliases to refer
> back to the defined keystores depending on the use case.
> 
> Here's an example:
> 
>    <Keystore
>          name="firstKeystore"
>          truststoreFile="..." (and other truststore attributes)
>          keystoreFile="..." (and other keystore attributes)
>          [other allowed configuration attributes]>


This is pretty much what my <TLSAlias> proposal was, except that they would be explicitly-referenced from <Connector> and/or <Host> instead of being nested within.
Comment 12 Mark Thomas 2015-04-09 11:26:02 UTC
Keeping the config at the connector level is probably the way to go. There are weird and wonderful configuration possibilities like one Connector on one interface with one set of certs for internal users and another connector on another interface with another set of certs for external users that share the same hosts.

I think we should keep the TLS cert <-> host name mapping completely independent from the Host <-> host name mapping. Most folks will have them aligned but some will want to do soemthign different. Using <Alias>...</Alias> should allow some config copy/paste for those that want to.

SNI is mandatory for HTTP/2 so this has just jumped to the top of my TODO list.

I'm thinking along the lines of the the configuration style in comment#7.

I've also been thinking about trying to merge the JSSE and OpenSSL configuration attributes. I'm not sure if it will work but the idea is to deprecate setting these on the connector and add a defaultTLSAlias="" element to the Connector that references the cert to use if nothing else matches. If the existing configuration attributes are used on the Connector then they are mapped to a TLSAlias element with a pre-defined name (probably default or something similar), along with a deprecated config warning.

I don't know how feasible this merging plan is but if it works, in addition to simpler config, it should allow further simplification of the Http11*Protocol implementations.
Comment 13 quartz 2015-04-10 13:26:58 UTC
I have a few technical heads up.

JDK8 doesn't have a clear way to intercept the SNI before chosign the keystore's alias. This would need some tricks. I'm not completely sure of either, but it's worth discussing I think.

a) anonymous SNI matcher leaking server name into threadlocal "connection context".
As you know, the whole ssl handshake is pretty closed, with a few callbacks. We would need a custom x509extendedkeymanager that allows to chose an alias from a key store (does this mean all keys are in the same keystore?). In order to pick the right alias, such custom key manager would have to consult a threadlocal object "connection context" (a new tomcat class to create) where the SNI info from the SNImatcher was populated (leaked). The custom SNI matcher (pretty much always accepts, but act mainly as an observer) would simply leak the server name it received on the threadlocal connection context..

Warning, this is dangerous with NIO where a single thread can perform handshake for many connections (I presume NIO was implemented for TLS, but maybe not; regardless, this is a possibility for the future). Therefore, the connection context must be switched in and out of the thread local in alignement with the socket being worked on obviously. I also presume the SSL engine and SSL socket jdk stuff is not multithreaded in their implementation in order to enable passing information in a threadlocal.


b) look ahead of the client hello tls record.
This is the process mentioned in the linked article above. It requires implementing solid TLS parsers (including the upcoming TLS 1.3). Once the plain tcp socket is opened, the stream bytes are read and inspected for the SNI, rewound, the ssl context is setup and then the sslsocket is hooked to the stream to handshake and all. It looks a lot cleaner, if not simpler. No sni matcher needed, no custom x509 key namager. Just standard jsse. However, it is expected to be slower too, since the client hello is parsed twice, and it is sensitive to TLS spec changes, instead of counting on the jdk folks.

What do you think?
Comment 14 Mark Thomas 2015-04-10 13:45:01 UTC
I think if you were following the dev list you would have seen that b) has already been implemented for NIO.

So far, there don't appear to be any changes to the ClientHello in TLS 1.3. We can cross that bridge as and when we come to it.

Real world use cases suggest that only a few hundred bytes need to be processed to extract the SNI information, much of which can simply be skipped once the length is known. Given the overhead of creating a TLS connection, I'm not worried about the overhead this approach adds.
Comment 15 quartz 2015-04-10 21:32:00 UTC
nio: ok. Sorry.

As for TLS parsing, there can be a whole lot of stuff well beyond 100 bytes in client hello, namely yet unknown extensions. TLS records proto msg length is up to 2^14-1 bytes. Not an issue I guess.

Also, I just read that with a DHE handshake the SNI could come later and encrypted.
https://tools.ietf.org/html/draft-rescorla-tls13-new-flows-01#section-4

So, if they have it their way, it won't be enough to look ahead the clienthello in 1.3. But I won't bet on that delayed encrypted sni; it is paranoiac protection because the DNS lookup just before is pretty much revealing the same info.
Comment 16 Mark Thomas 2015-04-29 21:40:56 UTC
This is implemented in Tomcat 9 for NIO, NIO2 and APR/native.

Note that the configuration refactoring isn't complete yet but the building blocks are all in place.