An Empirical Study of SNI Support in Different HTTP Clients

An Empirical Study of SNI Support in Different HTTP Clients #

2013-10-05

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:

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:

  • wget before 1.14
  • Java before v7
  • Python 2.x (ssl, urllib[2], httplib)
  • IE on XP + v6 and earler

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 AgentOperating SystemResult
Curl 7.19.7 (OpenSSL 1.0.0)CentOS 6.4OK
Wget 1.12CentOS 6.4Works on first cert, fails on second
Php 5.3.3, curlCentOS 6.4OK
Python 2.6.6 (urllib2)CentOS 6.4OK
Java 1.6.0_24CentOS 6.4Works on first cert, fails on second
Java 1.7.0_25CentOS 6.4OK
Curl 7.26.0 (OpenSSL 1.0.0)Debian 7.0 (wheezy)OK
Wget 1.13.4Debian 7.0 (wheezy)Works on first cert, fails on second
Php 5.4.4Debian 7.0 (wheezy)OK
Python 2.7.3Debian 7.0 (wheezy)OK
Java 1.6.0_27Debian 7.0 (wheezy)Works on first cert, fails on second
Java 1.7.0_25Debian 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.12Ubuntu 10.04 (lucid)OK
Php 5.3.2, curlUbuntu 10.04 (lucid)OK
Python 2.6.5, urllib2Ubuntu 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, curlUbuntu 12.04 (precise)OK
Python 2.7.3, urllib2Ubuntu 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.0Ubuntu 12.04 (precise)OK
Curl 7.31.0 (OpenSSL 1.0.1e)OmniOS v11 r151006OK
Wget 1.14OmniOS v11 r151006OK
Php 5.3.27 (OpenSSL 1.0.0)OmniOS v11 r151006OK
Php 5.4.19 (OpenSSL 1.0.0)OmniOS v11 r151006OK
Python 2.6.7OmniOS v11 r151006OK
Java 1.6.0_26-b03OmniOS v11 r151006Works on first cert, fails on second
Ruby 1.9.3p194 (open-uri)OmniOS v11 r151006OK
Curl 7.26.0OpenBSD 5.2Works on second cert, fails on first
Wget 1.13.4OpenBSD 5.2Neither cert works
Python 2.7.3OpenBSD 5.2OK
Curl 7.24.0 (OpenSSL/0.9.8y)Mac OSX 10.8.5OK
Wget 1.14Mac OSX 10.8.5OK
Php 5.3.26, curlMac OSX 10.8.5OK
Python 2.7.2, urllib2Mac OSX 10.8.5OK
Java 6 (1.6.0_51)Mac OSX 10.8.5Works on first cert, fails on second
Ruby 1.8.7 (open-uri)Mac OSX 10.8.5Works on first cert, fails on second
Chrome 29.0.1547.76Mac OSX 10.8.5OK
Safari 6.0.5Mac OSX 10.8.5OK
Firefox 23.0.1Mac OSX 10.8.5OK
IE 7.0.5730.13Windows XP SP3Works on first cert, fails on second
Firefox 15.0.1Windows XP SP3OK
IE 9.0.8112.16421Windows 7 SP1OK
Chrome 29.0.1547.76Windows 7 SP1OK

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:

  • Perl
  • Python httplib module and ssl module
  • Write a wrapper script to automate all of the testing

References #

http://nginx.org/en/docs/http/configuring_https_servers.html

http://en.wikipedia.org/wiki/Server_Name_Indication