Redirect to SSL only if browser supports SNI
I have Apache 2.2 with mod_ssl and a bunch of sites in HTTPS on the same IP/port with VirtualHosting, so client must support SNI to connect to those virtual hosts.
I would like to configure my server the following way:
When a user types www.dummysite.com and his browser supports SNI (Server Name Indication), any HTTP request is redirected to https://
where an HSTS header is sent. But if the browser doesn't support SNI then the request is served by HTTP.
The above rule, stated as is, is actually a fallback rule for those people that still run old browsers, as Mozilla and Chrome don't have this problem, just to avoid leaving these users out of the site.
I would like to do this redirecting at the Apache config level, perhaps with a filter on the user agent. I wouldn't like to touch running applications except making sure that no direct http:// references are present (otherwise they imply a security warning)
[Edit] (while editing the question I forgot the question): what is the list of SNI-enabled user agents to redirect?
Since SNI occurs during the SSL/TLS handshake, it's not possible to detect browser support when the client connects to HTTP.
So, you're right; a user-agent filter is the only way to do this.
The big question is whether you want to act on a blacklist against browsers that you know won't listen for SNI, or a whitelist of browsers that are known to support it. Obscure or new devices being unable to use the site seems like a deal-breaker, so I'd say the whitelist might be the better option.
In your HTTP <VirtualHost>
:
# Internet Explorer 7, 8, 9, on Vista or newer
RewriteCond %{HTTP_USER_AGENT} MSIE\s7.*Windows\sNT\s6 [OR]
RewriteCond %{HTTP_USER_AGENT} MSIE\s8.*Windows\sNT\s6 [OR]
RewriteCond %{HTTP_USER_AGENT} MSIE\s9.*Windows\sNT\s6 [OR]
# Chrome on Windows, Mac, Linux
RewriteCond %{HTTP_USER_AGENT} Windows\sNT\s6.*Chrome [OR]
RewriteCond %{HTTP_USER_AGENT} Macintosh.*Chrome [OR]
RewriteCond %{HTTP_USER_AGENT} Linux.*Chrome [OR]
# Firefox - we'll just make the assumption that all versions in the wild support:
RewriteCond %{HTTP_USER_AGENT} Gecko.*Firefox
RewriteRule ^/(.*)$ https://ssl.hostname/$1 [R=301]
Here's the blacklist option, too - keep in mind that this runs the risk of sending a client that doesn't use SNI to an SNI-needed site, but on the other hand, will send users of something new like IE 10 to the right place:
# IE 6
RewriteCond %{HTTP_USER_AGENT} !MSIE\s6
# Windows XP/2003
RewriteCond %{HTTP_USER_AGENT} !Windows\sNT\s5
# etc etc
RewriteRule ^/(.*)$ https://ssl.hostname/$1 [R=301]
There are a lot of browsers out there. I've been pretty loose with the expressions and haven't covered a lot of browsers - this could turn into quite the nightmare to maintain.
Whichever option you choose.. good luck!
My solution is this:
# Test if SNI will work and if not redirect to too old browser page
RewriteCond %{HTTPS} on
RewriteCond %{SSL:SSL_TLS_SNI} =""
RewriteRule ^ http://www.example.com/too-old-browser [L,R=307]
If an old browser without SNI tries to access https://www.example.com/* then it will throw a error on the browser first, which cannot be avoided since until apache replies to a non-SNI browser it doesn't know which site it is asking for. Then it redirects to a page telling the user their browser is too old (as long as user clicks continue to website).
And for users with new browsers I have
#Test if new browser and if so redirect to https
#new browser is not MSIE 5-8, not Android 0-3
RewriteCond %{HTTPS} off
RewriteCond %{HTTP_USER_AGENT} !MSIE\ [5-8]
RewriteCond %{HTTP_USER_AGENT} !Android.*(Mobile)?\ [0-3]
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
That excludes most of the old browsers, including some like MSIE 5-8 on Vista (9+ is only Vista/7 so supports SNI). It's not 100% (symbian is ignored etc.) but should work for the majority. The minority can still choose to accept the certificate error.
As far as I'm aware there's not really a good way to do this -- You can use a mod_rewrite rule or similar conditionally based on the User-agent
header, but it would have to be on a NON-SSL vhost: If the browser does not support SNI and it goes to a secure (https://
) site it's going to get the old-school Apache behavior of "Here's the first SSL certificate I have associated with that IP address -- Hope it's what you wanted!" -- If that's not the certificate the browser was expecting you'll wind up with an error message about hostname mismatches.
This basically means people have to hit a non-SSL interstitial page that will redirect them - possibly exposing any data they're sending in their request. This may or may not be a deal breaker (you say you're going to send them to a non-SSL site anyway if they don't support SNI, so I assume you don't care that much about security. If I were designing a system that had a need for SSL as an encryption or authentication layer I'd be a bit more insistent about it though...)
None of that stops someone from bookmarking the secure site though - and if they use a shared bookmark service or restore their bookmarks to a machine where the web browser doesn't support SNI they're back in the Potential-for-SSL-Errors case.
I'd be tempted to solve this one of three ways:
-
RewriteRule
based onUser-Agent
headers. - Load a https:// URI in a
<SCRIPT>
tag on a non-default VHost; if the load succeeds, it's a bit of JS that reloads the whole page under HTTPS. - Teach my visitors to use something like HTTPS Everywhere if this is a priority for them, force HTTPS on pages where it should be required, and hope everything works out in the end.
Of these, I personally like #2 the best, but that involves modifying your sites' code.