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 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:
- 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