An Empirical Study of SNI Support in Different HTTP Clients

There’s a bit of a chicken and egg situation inherent in the way that SSL works. This can make hosting multiple SSL domains/certs on the same host problematic. Before an SSL client can make a request it must handshake with the server to set up a secure connection. Part of the handshake process is the server presenting it’s SSL cert to the client. Only after the connection is set up does the client send the actual HTTP request. And here’s the problem : when the server hosts multiple certs, it can’t know which cert to present to the client because the client only tells it in the HTTP request - in the form of the Host header.

There are some workarounds for this. You can run each SSL cert on a different IP, for example, but this usually isn’t much good in cloud environments.

SNI (Server Name Indication, RFC 6066) is an extension to SSL which aims to solve this problem. It allows the client to specify which domain it’s making a request to as the connection is being established. There have been a few instances in the past where SNI would have really simplified some piece of infrastructure I was setting up. But, I never actually implemented it as I was alway put off by concerns around the hit-and-miss support for it on the client side. So, I decided to actually sit down and test a bunch of HTTP clients and see whether they worked or not. As you’ll see from the results below, those concerns seem to be some be somewhat validated in the results below. If you’re running an API, for example, there are plenty of gaps in SNI support. Most notably Java 6 and Ruby 1.8.

The server-side test rig setup

I set up Nginx with two proper SSL certs. Let’s call them domain1.com and domain2.com. One server block for each, like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  server {
    listen 443;
    index index.html;
    server_name *.domain1.com;
    root /var/www/domain1.com;
    ssl on;
    ssl_certificate /usr/local/etc/nginx/ssl/domain1.crt;
    ssl_certificate_key /usr/local/etc/nginx/ssl/domain1.key;
  }

  server {
    listen 443;
    index index.html;
    server_name *.domain2.com;
    root /var/www/domain2.com;
    ssl on;
    ssl_certificate /usr/local/etc/nginx/ssl/domain2.crt;
    ssl_certificate_key /usr/local/etc/nginx/ssl/domain2.key;
  }

I’m using Nginx 1.4.1 from brew on my laptop, nothing fancy. You can check for SNI support by running nginx -V.

  laptop:~ chrskly$ /usr/local/Cellar/nginx/1.4.1/bin/nginx -V
  nginx version: nginx/1.4.1
  built by clang 4.0 (tags/Apple/clang-421.0.60) (based on LLVM 3.1svn)
  TLS SNI support enabled
  configure arguments: --prefix=/usr/local/Cellar/nginx/1.4.1 --with-http_ssl_module --with-pcre --with-ipv6 --sbin-path=/usr/local/Cellar/nginx/1.4.1/bin/nginx --with-cc-opt=-I/usr/local/include --with-ld-opt=-L/usr/local/lib --conf-path=/usr/local/etc/nginx/nginx.conf --pid-path=/usr/local/var/run/nginx.pid --lock-path=/usr/local/var/run/nginx.lock --http-client-body-temp-path=/usr/local/var/run/nginx/client_body_temp --http-proxy-temp-path=/usr/local/var/run/nginx/proxy_temp --http-fastcgi-temp-path=/usr/local/var/run/nginx/fastcgi_temp --http-uwsgi-temp-path=/usr/local/var/run/nginx/uwsgi_temp --http-scgi-temp-path=/usr/local/var/run/nginx/scgi_temp --http-log-path=/usr/local/var/log/nginx --with-http_gzip_static_module

Expectations

According to wikipedia the following do not work:

The Results

I tested multiple operating systems, mostly with vagrant. I only tested whatever was available in the relevant OS-provided package repositories. In some cases there were multiple version available, e.g. java 1.6 and 1.7. I saved the vagrant configs and some bits of puppet to make generating the test machines a bit easier. You can grab a copy of it over on github : https://github.com/chrskly/I-SNI-what-you-did-there

HTTP Agent Operating System Result
Curl 7.19.7 (OpenSSL 1.0.0) CentOS 6.4 OK
Wget 1.12 CentOS 6.4 Works on first cert, fails on second
Php 5.3.3, curl CentOS 6.4 OK
Python 2.6.6 (urllib2) CentOS 6.4 OK
Java 1.6.0_24 CentOS 6.4 Works on first cert, fails on second
Java 1.7.0_25 CentOS 6.4 OK
Curl 7.26.0 (OpenSSL 1.0.0) Debian 7.0 (wheezy) OK
Wget 1.13.4 Debian 7.0 (wheezy) Works on first cert, fails on second
Php 5.4.4 Debian 7.0 (wheezy) OK
Python 2.7.3 Debian 7.0 (wheezy) OK
Java 1.6.0_27 Debian 7.0 (wheezy) Works on first cert, fails on second
Java 1.7.0_25 Debian 7.0 (wheezy) OK
Ruby 1.8.7 (open-uri) Debian 7.0 (wheezy) Works on first cert, fails on second
Ruby 1.9.3p194 (open-uri) Debian 7.0 (wheezy) OK
Curl 7.19.7 (OpenSSL 0.9.8) Ubuntu 10.04 (lucid) OK
Wget 1.12 Ubuntu 10.04 (lucid) OK
Php 5.3.2, curl Ubuntu 10.04 (lucid) OK
Python 2.6.5, urllib2 Ubuntu 10.04 (lucid) OK
Java 6 (6b27-1.12.6) Ubuntu 10.04 (lucid) Works on first cert, fails on second
Ruby 1.8 (open-uri) Ubuntu 10.04 (lucid) Works on first cert, fails on second
Curl 7.22.0 (OpenSSL 1.0.0) Ubuntu 12.04 (precise) OK
Wget 1.13.4 (OpenSSL 1.0.0) Ubuntu 12.04 (precise) Works on first cert, fails on second
Php 5.3.10, curl Ubuntu 12.04 (precise) OK
Python 2.7.3, urllib2 Ubuntu 12.04 (precise) OK
Java 6 (6b27-1.12.6) Ubuntu 12.04 (precise) Works on first cert, fails on second
Java 7 (7u25-2.3.10-1ubuntu0.12.04.2) Ubuntu 12.04 (precise) OK
Ruby 1.8.7.352 (open-uri) Ubuntu 12.04 (precise) Works on first cert, fails on second
Ruby 1.9.3.0 Ubuntu 12.04 (precise) OK
Curl 7.31.0 (OpenSSL 1.0.1e) OmniOS v11 r151006 OK
Wget 1.14 OmniOS v11 r151006 OK
Php 5.3.27 (OpenSSL 1.0.0) OmniOS v11 r151006 OK
Php 5.4.19 (OpenSSL 1.0.0) OmniOS v11 r151006 OK
Python 2.6.7 OmniOS v11 r151006 OK
Java 1.6.0_26-b03 OmniOS v11 r151006 Works on first cert, fails on second
Ruby 1.9.3p194 (open-uri) OmniOS v11 r151006 OK
Curl 7.26.0 OpenBSD 5.2 Works on second cert, fails on first
Wget 1.13.4 OpenBSD 5.2 Neither cert works
Python 2.7.3 OpenBSD 5.2 OK
Curl 7.24.0 (OpenSSL/0.9.8y) Mac OSX 10.8.5 OK
Wget 1.14 Mac OSX 10.8.5 OK
Php 5.3.26, curl Mac OSX 10.8.5 OK
Python 2.7.2, urllib2 Mac OSX 10.8.5 OK
Java 6 (1.6.0_51) Mac OSX 10.8.5 Works on first cert, fails on second
Ruby 1.8.7 (open-uri) Mac OSX 10.8.5 Works on first cert, fails on second
Chrome 29.0.1547.76 Mac OSX 10.8.5 OK
Safari 6.0.5 Mac OSX 10.8.5 OK
Firefox 23.0.1 Mac OSX 10.8.5 OK
IE 7.0.5730.13 Windows XP SP3 Works on first cert, fails on second
Firefox 15.0.1 Windows XP SP3 OK
IE 9.0.8112.16421 Windows 7 SP1 OK
Chrome 29.0.1547.76 Windows 7 SP1 OK

Conclusion

So, as you can see, there are a lot of gaps in the results. Even in recent OS releases, support is missing in a lot of places. If you’re thinking of using SNI, you might want to think again.

Some other things to test/do:

References

http://nginx.org/en/docs/http/configuring_https_servers.html http://en.wikipedia.org/wiki/Server_Name_Indication