Redirect to SSL only if browser supports SNI

Solution 1:

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!

Solution 2:

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 ^ [L,R=307]

If an old browser without SNI tries to access* 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.

Solution 3:

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.