TLS Server Name Indication
Problem this snippet solves:
Extensions to TLS encryption protocols after TLS v1.0 have added support for passing the desired servername as part of the initial encryption negotiation. This functionality makes it possible to use different SSL certificates with a single IP address by changing the server's response based on this field. This process is called Server Name Indication (http://en.wikipedia.org/wiki/Server_Name_Indication).
It is not supported on all browsers, but has a high level of support among widely-used browsers. Only use this functionality if you know the bulk of the browsers accessing your site support SNI - the fact that IE on Windows XP does not precludes the wide use of this functionality for most sites, but only for now. As older browsers begin to die off, SNI will be a good weapon in your arsenal of virtual hosting tools.
You can test if your browser supports SNI by clicking here: https://alice.sni.velox.ch/
Supported Browsers: * Internet Explorer 7 or later, on Windows Vista or higher * Mozilla Firefox 2.0 or later * Opera 8.0 or later (the TLS 1.1 protocol must be enabled) * Opera Mobile at least version 10.1 beta on Android * Google Chrome (Vista or higher. XP on Chrome 6 or newer) * Safari 2.1 or later (Mac OS X 10.5.6 or higher and Windows Vista or higher) * MobileSafari in Apple iOS 4.0 or later (8) * Windows Phone 7 * MicroB on Maemo
Unsupported Browsers: * Konqueror/KDE in any version * Internet Explorer (any version) on Windows XP * Safari on Windows XP * wget * BlackBerry Browser * Windows Mobile up to 6.5 * Android default browser (Targeted for Honeycomb but won't be fixed until next version for phone users as Honeycomb will be reserved to tablets only) * Oracle Java JSSE
Note: The iRule listed here is only supported on v10 and above.
Note: Support for SNI was added in 11.1.0. See SOL13452 for more information.
How to use this snippet:
-
Create a string-type datagroup to be called "tls_servername". Each hostname that needs to be supported on the VIP must be input along with its matching clientssl profile. For example, for the site "testsite.site.com" with a ClientSSL profile named "clientssl_testsite", you should add the following values to the datagroup.
String: testsite.site.com Value: clientssl_testsite
-
If you wish to switch pool context at the time the servername is detected in TLS, then you need to create a string-type datagroup called "tls_servername_pool". You will input each hostname to be supported by the VIP and the pool to direct the traffic towards. For the site "testsite.site.com" to be directed to the pool "testsite_pool_80", add the following to the datagroup:
String: testsite.site.com Value: testsite_pool_80
-
Apply the iRule below to a chosen VIP. When applied, this iRule will detect if an SNI field is present and dynamically switch the SSL profile and pool to use the configured certificate.
Important: The VIP must have a clientSSL profile AND a default pool set. If you don't set this, the iRule will likely break. There is also no real errorhandling for incorrect/inaccurate entries in the datagroup lists -- if you enter a bad value, it'll fail.
This allows you to support multiple certificates and multiple pools per VS IP address.
when CLIENT_ACCEPTED {
if { [PROFILE::exists clientssl] } {
# We have a clientssl profile attached to this VIP but we need
# to find an SNI record in the client handshake. To do so, we'll
# disable SSL processing and collect the initial TCP payload.
set default_tls_pool [LB::server pool]
set detect_handshake 1
SSL::disable
TCP::collect
} else {
# No clientssl profile means we're not going to work.
log local0. "This iRule is applied to a VS that has no clientssl profile."
set detect_handshake 0
}
}
when CLIENT_DATA {
if { ($detect_handshake) } {
# If we're in a handshake detection, look for an SSL/TLS header.
binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen
# TLS is the only thing we want to process because it's the only
# version that allows the servername extension to be present. When we
# find a supported TLS version, we'll check to make sure we're getting
# only a Client Hello transaction -- those are the only ones we can pull
# the servername from prior to connection establishment.
switch $tls_version {
"769" -
"770" -
"771" {
if { ($tls_xacttype == 22) } {
binary scan [TCP::payload] @5c tls_action
if { not (($tls_action == 1) && ([TCP::payload length] > $tls_recordlen)) } {
set detect_handshake 0
}
}
}
default {
set detect_handshake 0
}
}
if { ($detect_handshake) } {
# If we made it this far, we're still processing a TLS client hello.
#
# Skip the TLS header (43 bytes in) and process the record body. For TLS/1.0 we
# expect this to contain only the session ID, cipher list, and compression
# list. All but the cipher list will be null since we're handling a new transaction
# (client hello) here. We have to determine how far out to parse the initial record
# so we can find the TLS extensions if they exist.
set record_offset 43
binary scan [TCP::payload] @${record_offset}c tls_sessidlen
set record_offset [expr {$record_offset + 1 + $tls_sessidlen}]
binary scan [TCP::payload] @${record_offset}S tls_ciphlen
set record_offset [expr {$record_offset + 2 + $tls_ciphlen}]
binary scan [TCP::payload] @${record_offset}c tls_complen
set record_offset [expr {$record_offset + 1 + $tls_complen}]
# If we're in TLS and we've not parsed all the payload in the record
# at this point, then we have TLS extensions to process. We will detect
# the TLS extension package and parse each record individually.
if { ([TCP::payload length] >= $record_offset) } {
binary scan [TCP::payload] @${record_offset}S tls_extenlen
set record_offset [expr {$record_offset + 2}]
binary scan [TCP::payload] @${record_offset}a* tls_extensions
# Loop through the TLS extension data looking for a type 00 extension
# record. This is the IANA code for server_name in the TLS transaction.
for { set x 0 } { $x < $tls_extenlen } { incr x 4 } {
set start [expr {$x}]
binary scan $tls_extensions @${start}SS etype elen
if { ($etype == "00") } {
# A servername record is present. Pull this value out of the packet data
# and save it for later use. We start 9 bytes into the record to bypass
# type, length, and SNI encoding header (which is itself 5 bytes long), and
# capture the servername text (minus the header).
set grabstart [expr {$start + 9}]
set grabend [expr {$elen - 5}]
binary scan $tls_extensions @${grabstart}A${grabend} tls_servername
set start [expr {$start + $elen}]
} else {
# Bypass all other TLS extensions.
set start [expr {$start + $elen}]
}
set x $start
}
# Check to see whether we got a servername indication from TLS. If so,
# make the appropriate changes.
if { ([info exists tls_servername] ) } {
# Look for a matching servername in the Data Group and pool.
set ssl_profile [class match -value [string tolower $tls_servername] equals tls_servername]
set tls_pool [class match -value [string tolower $tls_servername] equals tls_servername_pool]
if { $ssl_profile == "" } {
# No match, so we allow this to fall through to the "default"
# clientssl profile.
SSL::enable
} else {
# A match was found in the Data Group, so we will change the SSL
# profile to the one we found. Hide this activity from the iRules
# parser.
set ssl_profile_enable "SSL::profile $ssl_profile"
catch { eval $ssl_profile_enable }
if { not ($tls_pool == "") } {
pool $tls_pool
} else {
pool $default_tls_pool
}
SSL::enable
}
} else {
# No match because no SNI field was present. Fall through to the
# "default" SSL profile.
SSL::enable
}
} else {
# We're not in a handshake. Keep on using the currently set SSL profile
# for this transaction.
SSL::enable
}
# Hold down any further processing and release the TCP session further
# down the event loop.
set detect_handshake 0
TCP::release
} else {
# We've not been able to match an SNI field to an SSL profile. We will
# fall back to the "default" SSL profile selected (this might lead to
# certificate validation errors on non SNI-capable browsers.
set detect_handshake 0
SSL::enable
TCP::release
}
}
}
7 Comments
- Daniel_Alves_19
Nimbostratus
Hi anyone,
I have a question and in case to switch the traffic not just using SNI information, but the uri too, How can I change the code to this work ?. The flow of traffic it´ll be.
The connection gets to VIP, on this VIP there´s 5 certificates, so the SNI it´ll be checked out to decrypt the data, on the same VIP there´s an Irule to redirect traffic using uri to different pools, after this the traffic must to be encrypt again because the server has the certificates too.
Client ---- VIP:443 (5 certificates) SNI ->>> Irule redirect uri to pools ->>> | Certificates encrypt server side.
Anyone can help me ?
- Chad_Jenison
Nimbostratus
Is there any reason to think that the code for examining TLS ClientHello would break with TLS 1.3? If not, we'd want to modify this as per below, right?
from:
switch $tls_version { "769" - "770" - "771" {
to:
switch $tls_version { "769" - "770" - "771" - "772" {
- Stanislas_Piro2
Cumulonimbus
@Chad : In TLS 1.3 Draft you can read this:
legacy_record_version This value MUST be set to 0x0303 for all records generated by a TLS 1.3 implementation other than the ClientHello, where it MAY also be 0x0301 for compatibility purposes. This field is deprecated and MUST be ignored for all purposes. Previous versions of TLS would use other values in this field under some circumstances.
And I saw that TLS 1.2 still send TLS 1.0 version in protocol version value of CLIENT_HELLO packet but send version 1.2 in handshake protocol version.
- Chad_Jenison
Nimbostratus
@stanislas
Thanks, so you're saying that even with TLS 1.3, that TLS_version will be 771(Dec)/0x0303 and thus, the iRule should work unmodified with TLS 1.3.
- Stanislas_Piro2
Cumulonimbus
CLIENT_HELLO version is 0x301 in TLS 1.2 and will still be 0x301 in TLS 1.3.
Real tls version negotiation is in a new TLS extension in TLS 1.3
- Stanislas_Piro2
Cumulonimbus
@chad, I tried my own code SNI based Pool selection without clientssl profile with firefox configured with TLS 1.3 support (draft 18).
The CLIENT_HELLO had following values (seen with wireshark):
- TLS record value : TLS 1.0
- TLS Handshake version : TLS 1.2
-
supported versions extension:
- TLS 1.3 (draft 18) 0x7f12
- TLS 1.2 0x0303
- TLS 1.1 0x0302
- TLS 1.0 0x0301
- server name : requested site
the code extracted the server name value like in TLS 1.2 CLIENT_HELLO.
- Ali_Khan
Nimbostratus
Hi Stan, I was going through a possible solution when i found this, can't ope the link and can't see your. Any chance you can re-share it please?
Basically trying to set a variable at CLIENT_HELLO with SNI recieved and then load balance to specific pool based on that.
Will appreciate your help.
Regards,
Ali