TLS Fingerprinting - a method for identifying a TLS client without decrypting
Hello, Kevin Stewart here. A while back someone asked an interesting question in the DevCentral forum about selecting a client SSL profile based on the device (ex. iOS, Android, Windows Phone). Normally you'd use a browser User-Agent HTTP header to identify the client user agent, but in this case, and based on the OSI model, you wouldn't be able to select an SSL profile (OSI layer 6) based on a User-Agent HTTP header (OSI layer 7), because at this point in time you don't yet have the layer 7 data - it's still encrypted. You could, however, use layer 3 or 4 data (IPs and ports), but that's generally not useful for identifying the client user agent. But there might still be a way...
Lee Brotherston has discovered that during an SSL handshake, most client user agents (different browsers, Dropbox, Skype, etc.) will initiate an SSL handshake request in an ever-so-unique way. The ordered combination of TLS version, record TLS version, ciphersuites, compression options, list of extensions, elliptic curves and signature algorithms are all specific enough that you can actually build a signature based on that data, and the collection of signatures into a database. From that discovery Lee created a project called "tls-fingerprinting". Please check it out. Now certainly, a client's ClientHello could be modified to support different ciphersuites and other features, the same way you could spoof a User-Agent HTTP header. However this modification will often lower security by re-introducing previously unsupported options, or in many cases modification to the user agent's SSL parameters isn't easy or isn't possible.
So given that we now have a new way to identify a user agent based on the client's ClientHello (the first message in the SSL handshake), I decided to re-visit the original DC forum request by integrating Lee's database into an iRules-based solution. The code example in the aforementioned thread just used the client's ciphersuite list, however, so today I'm going to expand on that and use all of the paramaters from the tls-fingerprinting database. Before we get started, I should mention two things:
This article is going to be long (sorry about that), and
We're going to break it down into a few phases, specifically
Defining the values in the tls-fingerprinting signature
Exporting and converting the tls-fingerprinting database to a BIG-IP external data group
Creating a fingerprintTLS PROC iRule (name this "Library-Rule")
Creating the caller iRule
So let's get started.
Defining the values in the tls-fingerprinting signature
Here's an example signature entry in Lee's tls-fingerprinting database (JSON version):
This is a pretty straight forward set of JSON key-value pairs. And if you're curious about what any of these values mean, I urge you to fire up Wireshark, open a browser to some HTTPS site, and then find a ClientHello message in the capture. You'll see all of these values and more, except for the first two, in that message. Our job then is to a) export the set of signatures to a BIG-IP external data group, and b) create an iRule that extracts all of these values from the client's ClientHello and compares those to the set of signatures in the data group. Of course iRules don't natively support JSON parsing, and while yes I could use iRulesLX for this, I decided to simply reformat the signatures in the data group to something more condusive to TCL iRules.
Exporting and converting the tls-fingerprinting database to a BIG-IP external data group
I don't really care about the "id" value, so I'll leave that out. And the "desc" field will be the value in the data group. The key will be the concatenation of all of the remaining fields.
"signature_data" := "ThunderBird (v38.0.1 OS X)",
I'm also going to remove the "0x" from the hex values, remove whitespace, and delimit each field with the plus (+) sign, so the resulting key for the above signature will look like this:
2. Convert it - use the following BASH script to extract each of the fields from each of the signatures. I should warn you now that my sed/awk/grep foo isn't strong, so I borrowed a BASH JSON parser from here: https://gist.github.com/cjus/1047794
#!/bin/bash
function jsonval () {
## description
desc=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "desc" |awk -F": " '{print $2}'`
## record_tls_version
rect=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "record_tls_version" |awk -F": " '{print $2}' |sed 's/0x//g'`
if [ -z "$rect" ]; then rect="@@@@"; fi
## tls_version
tlsv=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "tls_version" |awk -F": " '{print $2}' |sed 's/0x//g'`
if [ -z "$tlsv" ]; then tlsv="@@@@"; fi
## ciphersuite_length
cipl=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "ciphersuite_length" |awk -F": " '{print $2}' |sed 's/0x//g' |sed 's/ //g'`
if [ -z "$cipl" ]; then cipl="@@@@"; fi
## ciphersuite
ciph=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "ciphersuite" |awk -F": " '{print $2}' |sed 's/0x//g' |sed 's/ //g'`
if [ -z "$ciph" ]; then tlsv="ciph"; fi
## compression_length
coml=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "compression_length" |awk -F": " '{print $2}'`
if [ -z "$coml" ]; then coml="@@@@"; fi
## compression
comp=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "compression" |awk -F": " '{print $2}' |sed 's/0x//g' |sed 's/ //g'`
if [ -z "$comp" ]; then comp="@@@@"; fi
## extensions
exte=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "extensions" |awk -F": " '{print $2}' |sed 's/0x//g' |sed 's/ //g'`
if [ -z "$exte" ]; then exte="@@@@"; fi
## e_curves
ecur=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "e_curves" |awk -F": " '{print $2}' |sed 's/0x//g' |sed 's/ //g'`
if [ -z "$ecur" ]; then ecur="@@@@"; fi
## sig_alg
siga=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "sig_alg" |awk -F": " '{print $2}' |sed 's/0x//g' |sed 's/ //g'`
if [ -z "$siga" ]; then siga="@@@@"; fi
## ec_point_fmt
ecfp=`echo $1 | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w "ec_point_fmt" |awk -F": " '{print $2}' |sed 's/0x//g' |sed 's/ //g'`
if [ -z "$ecfp" ]; then ecfp="@@@@"; fi
echo "\"$rect+$tlsv+$cipl+$ciph+$coml+$comp+$exte+$ecur+$siga+$ecfp\" := \"$desc\","
}
IFS=}
for i in `cat fingerprint.db`; do
jsonval $i
done
Create this BASH script however you like (VI, VIM, Joe, Nano, whatever), save it, chmod it so that it'll execute ('chmod 755 parser.sh'), and then run it ('./parser.sh'). It'll just echo the reformatted signatures to the screen, so you'll want to capture that as a file ('./parser.sh > fingerprint.dg'). It's also be a little slow, again due to my aggregious lack of sed/awk/grep (and regex) foo, but it should still finish in less than a minute.
3. Import it - now go to the BIG-IP management UI, and under System - File Management - Data Group File List, click Import. Choose your reformatted text file, give it a meaningful name (ex. fingerprint_db), select "String" as the File Contents type, and use the same name (ex. fingerprint_db) in the Data Group Name field. On a v12 BIG-IP this will auto-create the local data group. On earlier systems you'll need to go manually create the local data group object that points to this external data group.
Creating a fingerprintTLS PROC iRule
So now that we've reformatted and imported the fingerprintTLS database, let's build the iRule to parse out the data from the client's ClientHello. I should also warn you that this process requires a lot of binary manipulation, so please don't try to ingest it all at once if you're new to iRules. I'm building this iRule as a separate PROC that other data plane iRules can call. It won't be directly attached to a virtual server.
## Library-Rule
## TLS Fingerprint Procedure #################
##
## Author: Kevin Stewart, 12/2016
## Derived from Lee Brotherston's "tls-fingerprinting" project @ https://github.com/LeeBrotherston/tls-fingerprinting
## Purpose: to identify the user agent based on unique characteristics of the TLS ClientHello message
## Input:
## Full TCP payload collected in CLIENT_DATA event of a TLS handshake ClientHello message
## Record length (rlen)
## TLS outer version (outer)
## TLS inner version (inner)
## Client IP
## Server IP
##############################################
proc fingerprintTLS { payload rlen outer inner clientip serverip } {
## The first 43 bytes of a ClientHello message are the record type, TLS versions, some length values and the
## handshake type. We should already know this stuff from the calling iRule. We're also going to be walking the
## packet, so the field_offset variable will be used to track where we are.
set field_offset 43
## The first value in the payload after the offset is the session ID, which may be empty. Grab the session ID length
## value and move the field_offset variable that many bytes forward to skip it.
binary scan ${payload} @${field_offset}c sessID_len
set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]
## The next value in the payload is the ciphersuite list length (how big the ciphersuite list is. We need the binary
## and hex values of this data.
binary scan ${payload} @${field_offset}S cipherList_len
binary scan ${payload} @${field_offset}H4 cipherList_len_hex
set cipherList_len_hex_text ${cipherList_len_hex}
## Now that we have the ciphersuite list length, let's offset the field_offset variable to skip over the length (2) bytes
## and go get the ciphersuite list. Multiple by 2 to get the number of appropriate hex characters.
set field_offset [expr {${field_offset} + 2}]
set cipherList_len_hex [expr {${cipherList_len} * 2}]
binary scan ${payload} @${field_offset}H${cipherList_len_hex} cipherlist
## Next is the compression method length and compression method. First move field_offset to skip past the ciphersuite
## list, then grab the compression method length. Then move field_offset past the length (2) bytes and grab the
## compression method value. Finally, move field_offset past the compression method bytes.
set field_offset [expr {${field_offset} + ${cipherList_len}}]
binary scan ${payload} @${field_offset}c compression_len
#set field_offset [expr {${field_offset} + ${compression_len}}]
set field_offset [expr {${field_offset} + 1}]
binary scan ${payload} @${field_offset}H[expr {${compression_len} * 2}] compression_type
set field_offset [expr {${field_offset} + ${compression_len}}]
## We should be in the extensions section now, so we're going to just run through the remaining data and
## pick out the extensions as we go. But first let's make sure there's more record data left, based on
## the current field_offset vs. rlen.
if { [expr {${field_offset} < ${rlen}}] } {
## There's extension data, so let's go get it. Skip the first 2 bytes that are the extensions length
set field_offset [expr {${field_offset} + 2}]
## Make a variable to store the extension types we find
set extensions_list ""
## Pad rlen by 1 byte
set rlen [expr ${rlen} + 1]
while { [expr {${field_offset} <= ${rlen}}] } {
## Grab the first 2 bytes to determine the extension type
binary scan ${payload} @${field_offset}H4 ext
## Store the extension in the extensions_list variable
append extensions_list ${ext}
## Increment field_offset past the 2 bytes of the extension type
set field_offset [expr {${field_offset} + 2}]
## Grab the 2 bytes of extension lenth
binary scan ${payload} @${field_offset}S ext_len
## Increment field_offset past the 2 bytes of the extension length
set field_offset [expr {${field_offset} + 2}]
## Look for specific extension types in case these need to increment the field_offset (and because we need their values)
switch $ext {
"000b" {
## ec_point_format - there's another 1 byte after length
## Grab the extension data
binary scan ${payload} @[expr {${field_offset} + 1}]H[expr {(${ext_len} - 1) * 2}] ext_data
set ec_point_format ${ext_data}
}
"000a" {
## elliptic_curves - there's another 2 bytes after length
## Grab the extension data
binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data
set elliptic_curves ${ext_data}
}
"000d" {
## sig_alg - there's another 2 bytes after length
## Grab the extension data
binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data
set sig_alg ${ext_data}
}
default {
## Grab the otherwise unknown extension data
binary scan ${payload} @${field_offset}H[expr {${ext_len} * 2}] ext_data
}
}
## Increment the field_offset past the extension data length. Repeat this loop until we reach rlen (the end of the payload)
set field_offset [expr {${field_offset} + ${ext_len}}]
}
}
## Now let's compile all of that data.
set cipl [string toupper ${cipherList_len_hex_text}]
set ciph [string toupper ${cipherlist}]
set coml ${compression_len}
set comp [string toupper ${compression_type}]
if { ( [info exists extensions_list] ) and ( ${extensions_list} ne "" ) } { set exte [string toupper ${extensions_list}] } else { set exte "@@@@" }
if { ( [info exists elliptic_curves] ) and ( ${elliptic_curves} ne "" ) } { set ecur [string toupper ${elliptic_curves}] } else { set ecur "@@@@" }
if { ( [info exists sig_alg] ) and ( ${sig_alg} ne "" ) } { set siga [string toupper ${sig_alg}] } else { set siga "@@@@" }
if { ( [info exists ec_point_format] ) and ( ${ec_point_format} ne "" ) } { set ecfp [string toupper ${ec_point_format}] } else { set ecfp "@@@@" }
## Initialize the match variable
set match ""
## Now let's build the fingerprint string and search the database
set fingerprint_str "${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}"
## Un-comment this line to display the fingerprint string in the LTM log for troubleshooting
## log local0. "${clientip}-${serverip}: fingerprint_str = ${fingerprint_str}"
if { [class match ${fingerprint_str} equals fingerprint_db] } {
## Direct match
set match [class match -value ${fingerprint_str} equals fingerprint_db]
} elseif { not ( ${ciph} starts_with "C0" ) and not ( ${ciph} starts_with "00" ) } {
## Hmm.. there's no direct match, which could either mean a database entry doesn't exist, or Chrome (and Opera) are adding
## special values to the cipherlist, extensions list and elliptic curves list.
## ex. 9A9A, 5A5A, EAEA, BABA, etc. at the beginning of the cipherlist
## Let's strip out these anomalous values and try the match again.
## Substract 2 bytes from cipherlist length
set cipl [format %04x [expr [expr 0x${cipl}] - 2]]
## Subtract 2 bytes from the front of the cipher list
set ciph [string range ${ciph} 4 end]
## Subtract 2 bytes from the front of the extensions list
set exte [string range ${exte} 4 end]
## There might be an additional random set in the string that needs to be removed (pattern is "(.)A\1A")
regsub {(.)A\1A} ${exte} "" exte
## If the above regsub doesn't work, try the following:
#regsub {(\wA)\1} ${exte} "" exte
## Subtract 2 bytes from the front of the elliptic curves list
set ecur [string range ${ecur} 4 end]
## Rebuild the fingerprint string
set fingerprint_str "${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}"
if { [class match ${fingerprint_str} equals fingerprint_db] } {
## Guess match
set match [class match -value ${fingerprint_str} equals fingerprint_db]
} else {
## No match
set match ""
}
}
## Return the matching user agent string
return ${match}
}
The PROC requires as input the full TCP payload (of the ClientHello message), the record length (extracted from the ClientHello message), the "outer" record TLS version and "inner" TLS version (also extracted form the ClientHello message). Using these values the PROC basically walks the payload looking for each of the required values (ciphersuite length, ciphersuite list, compression length, compression list, extensions list, elliptic curves, signature algorithms, and ec point formats). If any value doesn't exist in the payload (ex. the ClientHello doesn't contain a Sig_Alg field), that value is replaced with "@@@@". Once all of the values are found, the fingerprint string is created and used to search the data group. If there's a match, the user agent string (ex. ThunderBird (v38.0.1 OS X)) is returned to the caller. While testing this I noticed that newer versions of Chrome and Opera added what looked like "markers" to the ciphersuite list, extensions list, and elliptic curves list (ex. 9A9A, 5A5A, EAEA, BABA - always some alphanumeric value, followed by 'A', and repeated.). A cursory search didn't explain what these are, so maybe someone will know and report back. In the meantime, I added a "guess" function that removed these markers and tried the data group search again. All of the desktop browser testing (including Chrome and Opera) did get an accurate match with either the direct or guessed fingerprint, so I'll leave that in there until I find a better way to handle the markers.
Creating the caller iRule
The only thing left to do is to create the caller iRule. This iRule only needs to detect an SSL/TLS ClientHello, and then pass that to the fingerprint PROC. This is just a stub iRule to show the proper implementation. Once you've determined a TCP packet is an SSL/TLS handshake ClientHello, call the PROC and then do something useful with the resulting user agent string, like switch the client SSL profile.
when CLIENT_ACCEPTED {
## Collect the TCP payload
TCP::collect
}
when CLIENT_DATA {
## Get the TLS packet type and versions
if { ! [info exists rlen] } {
binary scan [TCP::payload] cH4ScH6H4 rtype outer_sslver rlen hs_type rilen inner_sslver
if { ( ${rtype} == 22 ) and ( ${hs_type} == 1 ) } {
## This is a TLS ClientHello message (22 = TLS handshake, 1 = ClientHello)
## Call the fingerprintTLS proc
set fingerprint [call Library-Rule::fingerprintTLS [TCP::payload] ${rlen} ${outer_sslver} ${inner_sslver} [IP::client_addr] [IP::local_addr]]
### Do Something here ###
log local0. "match = ${fingerprint}"
### Do Something here ###
}
}
# Collect the rest of the record if necessary
if { [TCP::payload length] < $rlen } {
TCP::collect $rlen
}
## Release the paylaod
TCP::release
}
What happens if there's no match?
Yes, there are some caveats...
It's safe to say that the tls-fingerprinting database isn't all inclusive. In fact it's FAR FROM COMPLETE and not always exact. I found, for example, that my version of Dropbox on a Win7 box (v16.4.30) doesn't make a match. It's nearly impossible to have the signature for every unique user agent every created, and all of the variations and versions of that agent. But what the database does have is the signatures for most browsers, so at the very least it makes for a nice way to whitelist browsers (vs. other agents). It also doesn't technically resolve the question in the original DC forum thread. The question was how to identify the device (ie. iOS, Android, Windows Phone), and for that you'd need some specific agent loaded on the device (not a browser) that could report that information. Mobile device management (MDM) solutions are particularly good at that sort of thing. The browser, Dropbox or other user agent on the mobile device may not specifically report the device (ex. "for iOS"). Some do, but I've found that most don't. At the end of this aticle I've included a few signatures that I found in my testing that aren't in the database. If your curious, uncomment the log local0. "${clientip}-${serverip}: fingerprint_str = ${fingerprint_str}" line in the fingerprintTLS PROC and then tail the LTM log. The caller iRule is already logging the returned user agent string, so if that is empty, you'll see the empty match in the log (match = ), preceded by the unmatched signature.
Where this project may be most useful is in outbound traffic management, where you want to decrypt and inspect the Internet-bound traffic, but cannot decrypt some user agents becuase of things like cerificate pinning. Since the pinning decision happens at the client, the only other recourse is to bypass decryption and inspection based on the destination host name or IP address, which can be a tedious thing to manage. TLS fingerprinting might allow you to simply decrypt and inspect for the user agents that you know aren't affected by pinning, specifically browsers. You'll potentially miss some things that you could have decrypted, but you'll save yourself the burden of managing an ever-growing list of pinner exclusions.
And on a final note, binary iRule manipulation is a very CPU-intensive thing to do. I could have very simply converted the raw payload to one long hex string (once) and walked that with string tools. I'll update the code when I have some time.
Thank you for writing this article. I have a couple of critiques, and a couple fingerprints to add to your list as well.
The caller iRule should have 'clientip' and 'serverip' arguments sent to Library-Rule.
With the assumption you wanted to log 'serverip' as the Virtual Server address, this is what the line should look like:
set fingerprint [call Library-Rule::fingerprintTLS [TCP::payload] ${rlen} ${outer_sslver} ${inner_sslver} [IP::client_addr] [IP::local_addr]]
Also, the 4th 'Additional Ciphers' string contains a double-quote in the middle of the string, causing copy-paste of your additional ciphers to fail with a syntax error.
If anybody would like to see missing fingerprints added to the main repo, I'm more than happy to do so. Pull requests are more than welcome, or if you prefer feel free to email me: lee+f5@squarelemon.com
<quote>
While testing this I noticed that newer versions of Chrome and Opera added what looked like "markers" to the ciphersuite list, extensions list, and elliptic curves list (ex. 9A9A, 5A5A, EAEA, BABA - always some alphanumeric value, followed by 'A', and repeated.). A cursory search didn't explain what these are, so maybe someone will know and report back.
</quote>
I know this is old article and by now you might have already figured it out. I am replying it for the readers just in case if they are not aware of this. the strange codes (e.g. 0x9a9a, etc) that you see are TLS GREASE values. For more info see the internet draft https://www.ietf.org/id/draft-ietf-tls-grease-04.txt (as of today it is in version 4).
Can we get an updated version of the fingerprintTLS procedure that corrects the four TCL warnings reported in BIG-IP versions 14 and 15 when running a "load sys config verify"? One was mentioned in a comment above. Two are trivial to resolve, but the other two aren't as obvious.
<quote>
It's also be a little slow, again due to my aggregious lack of sed/awk/grep (and regex) foo, but it should still finish in less than a minute.
</quote>
I've created a Python parser here that is fairly zippy.
"}},"componentScriptGroups({\"componentId\":\"custom.widget.Beta_Footer\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"component({\"componentId\":\"custom.widget.Tag_Manager_Helper\"})":{"__typename":"Component","render({\"context\":{\"component\":{\"entities\":[],\"props\":{}},\"page\":{\"entities\":[\"board:TechnicalArticles\",\"message:286251\"],\"name\":\"TkbMessagePage\",\"props\":{},\"url\":\"https://community.f5.com/kb/technicalarticles/tls-fingerprinting---a-method-for-identifying-a-tls-client-without-decrypting/286251\"}}})":{"__typename":"ComponentRenderResult","html":" "}},"componentScriptGroups({\"componentId\":\"custom.widget.Tag_Manager_Helper\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"component({\"componentId\":\"custom.widget.Consent_Blackbar\"})":{"__typename":"Component","render({\"context\":{\"component\":{\"entities\":[],\"props\":{}},\"page\":{\"entities\":[\"board:TechnicalArticles\",\"message:286251\"],\"name\":\"TkbMessagePage\",\"props\":{},\"url\":\"https://community.f5.com/kb/technicalarticles/tls-fingerprinting---a-method-for-identifying-a-tls-client-without-decrypting/286251\"}}})":{"__typename":"ComponentRenderResult","html":""}},"componentScriptGroups({\"componentId\":\"custom.widget.Consent_Blackbar\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/common/QueryHandler\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/community/NavbarDropdownToggle\"]})":[{"__ref":"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageView/MessageViewStandard\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageView/MessageViewStandard-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/ThreadedReplyList\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageReplyCallToAction\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageReplyCallToAction-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageSubject\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageSubject-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageBody\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageBody-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageCustomFields\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageCustomFields-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageRevision\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageRevision-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageReplyButton\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageReplyButton-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageAuthorBio\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/guides/GuideBottomNavigation\"]})":[{"__ref":"CachedAsset:text:en_US-components/guides/GuideBottomNavigation-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserLink\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserLink-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserRank\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserRank-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageTime\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageTime-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/customComponent/CustomComponent\"]})":[{"__ref":"CachedAsset:text:en_US-components/customComponent/CustomComponent-1743097588266"}],"message({\"id\":\"message:286252\"})":{"__ref":"TkbReplyMessage:message:286252"},"message({\"id\":\"message:286253\"})":{"__ref":"TkbReplyMessage:message:286253"},"message({\"id\":\"message:286254\"})":{"__ref":"TkbReplyMessage:message:286254"},"message({\"id\":\"message:286255\"})":{"__ref":"TkbReplyMessage:message:286255"},"message({\"id\":\"message:286256\"})":{"__ref":"TkbReplyMessage:message:286256"},"message({\"id\":\"message:286257\"})":{"__ref":"TkbReplyMessage:message:286257"},"message({\"id\":\"message:286258\"})":{"__ref":"TkbReplyMessage:message:286258"},"message({\"id\":\"message:286259\"})":{"__ref":"TkbReplyMessage:message:286259"},"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/ranks/UserRankLabel\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserRegistrationDate\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserRegistrationDate-1743097588266"}],"cachedText({\"lastModified\":\"1743097588266\",\"locale\":\"en-US\",\"namespaces\":[\"components/tags/TagView/TagViewChip\"]})":[{"__ref":"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1743097588266"}]},"CachedAsset:pages-1742462581816":{"__typename":"CachedAsset","id":"pages-1742462581816","value":[{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.MvpProgram","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/mvp-program","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"BlogViewAllPostsPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId/all-posts/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"CasePortalPage","type":"CASE_PORTAL","urlPath":"/caseportal","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"CreateGroupHubPage","type":"GROUP_HUB","urlPath":"/groups/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"CaseViewPage","type":"CASE_DETAILS","urlPath":"/case/:caseId/:caseNumber","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"InboxPage","type":"COMMUNITY","urlPath":"/inbox","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.AdvocacyProgram","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/advocacy-program","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetHelp.NonCustomer","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/non-customer","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HelpFAQPage","type":"COMMUNITY","urlPath":"/help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetHelp.F5Customer","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/f5-customer","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"IdeaMessagePage","type":"IDEA_POST","urlPath":"/idea/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"IdeaViewAllIdeasPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/all-ideas/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"LoginPage","type":"USER","urlPath":"/signin","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"BlogPostPage","type":"BLOG","urlPath":"/category/:categoryId/blogs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetInvolved","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.Learn","type":"COMMUNITY","urlPath":"/c/how-do-i/learn","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1739501996000,"localOverride":null,"page":{"id":"Test","type":"CUSTOM","urlPath":"/custom-test-2","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ThemeEditorPage","type":"COMMUNITY","urlPath":"/designer/themes","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"TkbViewAllArticlesPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId/all-articles/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"OccasionEditPage","type":"EVENT","urlPath":"/event/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"OAuthAuthorizationAllowPage","type":"USER","urlPath":"/auth/authorize/allow","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"PageEditorPage","type":"COMMUNITY","urlPath":"/designer/pages","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"PostPage","type":"COMMUNITY","urlPath":"/category/:categoryId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ForumBoardPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"TkbBoardPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"EventPostPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"UserBadgesPage","type":"COMMUNITY","urlPath":"/users/:login/:userId/badges","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"GroupHubMembershipAction","type":"GROUP_HUB","urlPath":"/membership/join/:nodeId/:membershipType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"MaintenancePage","type":"COMMUNITY","urlPath":"/maintenance","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"IdeaReplyPage","type":"IDEA_REPLY","urlPath":"/idea/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"UserSettingsPage","type":"USER","urlPath":"/mysettings/:userSettingsTab","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"GroupHubsPage","type":"GROUP_HUB","urlPath":"/groups","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ForumPostPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"OccasionRsvpActionPage","type":"OCCASION","urlPath":"/event/:boardId/:messageSubject/:messageId/rsvp/:responseType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"VerifyUserEmailPage","type":"USER","urlPath":"/verifyemail/:userId/:verifyEmailToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"AllOccasionsPage","type":"OCCASION","urlPath":"/category/:categoryId/events/:boardId/all-events/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"EventBoardPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"TkbReplyPage","type":"TKB_REPLY","urlPath":"/kb/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"IdeaBoardPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"CommunityGuideLinesPage","type":"COMMUNITY","urlPath":"/communityguidelines","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"CaseCreatePage","type":"SALESFORCE_CASE_CREATION","urlPath":"/caseportal/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"TkbEditPage","type":"TKB","urlPath":"/kb/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ForgotPasswordPage","type":"USER","urlPath":"/forgotpassword","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"IdeaEditPage","type":"IDEA","urlPath":"/idea/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"TagPage","type":"COMMUNITY","urlPath":"/tag/:tagName","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"BlogBoardPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"OccasionMessagePage","type":"OCCASION_TOPIC","urlPath":"/event/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ManageContentPage","type":"COMMUNITY","urlPath":"/managecontent","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ClosedMembershipNodeNonMembersPage","type":"GROUP_HUB","urlPath":"/closedgroup/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetHelp.Community","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/community","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"CommunityPage","type":"COMMUNITY","urlPath":"/","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetInvolved.ContributeCode","type":"COMMUNITY","urlPath":"/c/how-do-i/get-involved/contribute-code","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ForumMessagePage","type":"FORUM_TOPIC","urlPath":"/discussions/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"IdeaPostPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"BlogMessagePage","type":"BLOG_ARTICLE","urlPath":"/blog/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"RegistrationPage","type":"USER","urlPath":"/register","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"EditGroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ForumEditPage","type":"FORUM","urlPath":"/discussions/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ResetPasswordPage","type":"USER","urlPath":"/resetpassword/:userId/:resetPasswordToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"TkbMessagePage","type":"TKB_ARTICLE","urlPath":"/kb/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.Learn.AboutIrules","type":"COMMUNITY","urlPath":"/c/how-do-i/learn/about-irules","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"BlogEditPage","type":"BLOG","urlPath":"/blog/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetHelp.F5Support","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/f5-support","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ManageUsersPage","type":"USER","urlPath":"/users/manage/:tab?/:manageUsersTab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ForumReplyPage","type":"FORUM_REPLY","urlPath":"/discussions/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"PrivacyPolicyPage","type":"COMMUNITY","urlPath":"/privacypolicy","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"NotificationPage","type":"COMMUNITY","urlPath":"/notifications","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"UserPage","type":"USER","urlPath":"/users/:login/:userId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HealthCheckPage","type":"COMMUNITY","urlPath":"/health","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"OccasionReplyPage","type":"OCCASION_REPLY","urlPath":"/event/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ManageMembersPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/manage/:tab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"SearchResultsPage","type":"COMMUNITY","urlPath":"/search","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"BlogReplyPage","type":"BLOG_REPLY","urlPath":"/blog/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"GroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"TermsOfServicePage","type":"COMMUNITY","urlPath":"/termsofservice","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetHelp","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI.GetHelp.SecurityIncident","type":"COMMUNITY","urlPath":"/c/how-do-i/get-help/security-incident","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"CategoryPage","type":"CATEGORY","urlPath":"/category/:categoryId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"ForumViewAllTopicsPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/all-topics/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"TkbPostPage","type":"TKB","urlPath":"/category/:categoryId/kbs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"GroupHubPostPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1742462581816,"localOverride":null,"page":{"id":"HowDoI","type":"COMMUNITY","urlPath":"/c/how-do-i","__typename":"PageDescriptor"},"__typename":"PageResource"}],"localOverride":false},"CachedAsset:text:en_US-components/context/AppContext/AppContextProvider-0":{"__typename":"CachedAsset","id":"text:en_US-components/context/AppContext/AppContextProvider-0","value":{"noCommunity":"Cannot find community","noUser":"Cannot find current user","noNode":"Cannot find node with id {nodeId}","noMessage":"Cannot find message with id {messageId}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-0":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-0","value":{"title":"Loading..."},"localOverride":false},"User:user:-1":{"__typename":"User","id":"user:-1","uid":-1,"login":"Former Member","email":"","avatar":null,"rank":null,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":"ANONYMOUS","registrationTime":null,"confirmEmailStatus":false,"registrationAccessLevel":"VIEW","ssoRegistrationFields":[]},"ssoId":null,"profileSettings":{"__typename":"ProfileSettings","dateDisplayStyle":{"__typename":"InheritableStringSettingWithPossibleValues","key":"layout.friendly_dates_enabled","value":"false","localValue":"true","possibleValues":["true","false"]},"dateDisplayFormat":{"__typename":"InheritableStringSetting","key":"layout.format_pattern_date","value":"dd-MMM-yyyy","localValue":"MM-dd-yyyy"},"language":{"__typename":"InheritableStringSettingWithPossibleValues","key":"profile.language","value":"en-US","localValue":null,"possibleValues":["en-US"]}},"deleted":false},"Theme:customTheme1":{"__typename":"Theme","id":"customTheme1"},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bi0zNC0xM2k0MzE3N0Q2NjFBRDg5NDAy\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/bi0zNC0xM2k0MzE3N0Q2NjFBRDg5NDAy","mimeType":"image/png"},"Category:category:Articles":{"__typename":"Category","id":"category:Articles","entityType":"CATEGORY","displayId":"Articles","nodeType":"category","depth":1,"title":"Articles","shortTitle":"Articles","parent":{"__ref":"Category:category:top"},"categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:top":{"__typename":"Category","id":"category:top","displayId":"top","nodeType":"category","depth":0,"title":"Top","entityType":"CATEGORY","shortTitle":"Top"},"Tkb:board:TechnicalArticles":{"__typename":"Tkb","id":"board:TechnicalArticles","entityType":"TKB","displayId":"TechnicalArticles","nodeType":"board","depth":2,"conversationStyle":"TKB","title":"Technical Articles","description":"F5 SMEs share good practice.","avatar":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/bi0zNC0xM2k0MzE3N0Q2NjFBRDg5NDAy\"}"},"profileSettings":{"__typename":"ProfileSettings","language":null},"parent":{"__ref":"Category:category:Articles"},"ancestors":{"__typename":"CoreNodeConnection","edges":[{"__typename":"CoreNodeEdge","node":{"__ref":"Community:community:zihoc95639"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:Articles"}}]},"userContext":{"__typename":"NodeUserContext","canAddAttachments":false,"canUpdateNode":false,"canPostMessages":false,"isSubscribed":false},"boardPolicies":{"__typename":"BoardPolicies","canPublishArticleOnCreate":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","args":[]}},"canReadNode":{"__typename":"PolicyResult","failureReason":null}},"shortTitle":"Technical Articles","isManualSortOrderAvailable":false,"tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"repliesProperties":{"__typename":"RepliesProperties","sortOrder":"PUBLISH_TIME","repliesFormat":"threaded"},"eventPath":"category:Articles/community:zihoc95639board:TechnicalArticles/","tagProperties":{"__typename":"TagNodeProperties","tagsEnabled":{"__typename":"PolicyResult","failureReason":null}},"requireTags":true,"tagType":"FREEFORM_AND_PRESET"},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/cmstMjgtQ3U0RXo2\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/cmstMjgtQ3U0RXo2","height":0,"width":0,"mimeType":"image/svg+xml"},"Rank:rank:28":{"__typename":"Rank","id":"rank:28","position":5,"name":"Employee","color":"C20025","icon":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/cmstMjgtQ3U0RXo2\"}"},"rankStyle":"OUTLINE"},"User:user:130391":{"__typename":"User","id":"user:130391","uid":130391,"login":"Kevin_Stewart","deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/images/dS0xMzAzOTEtelZmemp2?image-coordinates=0%2C0%2C500%2C500"},"rank":{"__ref":"Rank:rank:28"},"email":"","messagesCount":5757,"biography":null,"topicsCount":39,"kudosReceivedCount":164,"kudosGivenCount":0,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2006-03-16T00:00:00.000-08:00","confirmEmailStatus":null},"followersCount":null,"solutionsCount":29,"entityType":"USER","eventPath":"community:zihoc95639/user:130391"},"TkbTopicMessage:message:286251":{"__typename":"TkbTopicMessage","uid":286251,"subject":"TLS Fingerprinting - a method for identifying a TLS client without decrypting","id":"message:286251","revisionNum":1,"repliesCount":8,"author":{"__ref":"User:user:130391"},"depth":0,"hasGivenKudo":false,"helpful":null,"board":{"__ref":"Tkb:board:TechnicalArticles"},"conversation":{"__ref":"Conversation:conversation:286251"},"messagePolicies":{"__typename":"MessagePolicies","canPublishArticleOnEdit":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","args":[]}},"canModerateSpamMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","args":[]}}},"contentWorkflow":{"__typename":"ContentWorkflow","state":"PUBLISH","scheduledPublishTime":null,"scheduledTimezone":null,"userContext":{"__typename":"MessageWorkflowContext","canSubmitForReview":null,"canEdit":false,"canRecall":null,"canSubmitForPublication":null,"canReturnToAuthor":null,"canPublish":null,"canReturnToReview":null,"canSchedule":false},"shortScheduledTimezone":null},"readOnly":false,"editFrozen":false,"moderationData":{"__ref":"ModerationData:moderation_data:286251"},"teaser":"","body":"
Hello, Kevin Stewart here. A while back someone asked an interesting question in the DevCentral forum about selecting a client SSL profile based on the device (ex. iOS, Android, Windows Phone). Normally you'd use a browser User-Agent HTTP header to identify the client user agent, but in this case, and based on the OSI model, you wouldn't be able to select an SSL profile (OSI layer 6) based on a User-Agent HTTP header (OSI layer 7), because at this point in time you don't yet have the layer 7 data - it's still encrypted. You could, however, use layer 3 or 4 data (IPs and ports), but that's generally not useful for identifying the client user agent. But there might still be a way...
\n\n
Lee Brotherston has discovered that during an SSL handshake, most client user agents (different browsers, Dropbox, Skype, etc.) will initiate an SSL handshake request in an ever-so-unique way. The ordered combination of TLS version, record TLS version, ciphersuites, compression options, list of extensions, elliptic curves and signature algorithms are all specific enough that you can actually build a signature based on that data, and the collection of signatures into a database. From that discovery Lee created a project called \"tls-fingerprinting\". Please check it out. Now certainly, a client's ClientHello could be modified to support different ciphersuites and other features, the same way you could spoof a User-Agent HTTP header. However this modification will often lower security by re-introducing previously unsupported options, or in many cases modification to the user agent's SSL parameters isn't easy or isn't possible.
\n\n
So given that we now have a new way to identify a user agent based on the client's ClientHello (the first message in the SSL handshake), I decided to re-visit the original DC forum request by integrating Lee's database into an iRules-based solution. The code example in the aforementioned thread just used the client's ciphersuite list, however, so today I'm going to expand on that and use all of the paramaters from the tls-fingerprinting database. Before we get started, I should mention two things:
\n\n
This article is going to be long (sorry about that), and
We're going to break it down into a few phases, specifically\n
Defining the values in the tls-fingerprinting signature
Exporting and converting the tls-fingerprinting database to a BIG-IP external data group
Creating a fingerprintTLS PROC iRule (name this \"Library-Rule\")
Creating the caller iRule
\n
\n\n
So let's get started.
\n\n
\n\n
Defining the values in the tls-fingerprinting signature
\n\n
Here's an example signature entry in Lee's tls-fingerprinting database (JSON version):
This is a pretty straight forward set of JSON key-value pairs. And if you're curious about what any of these values mean, I urge you to fire up Wireshark, open a browser to some HTTPS site, and then find a ClientHello message in the capture. You'll see all of these values and more, except for the first two, in that message. Our job then is to a) export the set of signatures to a BIG-IP external data group, and b) create an iRule that extracts all of these values from the client's ClientHello and compares those to the set of signatures in the data group. Of course iRules don't natively support JSON parsing, and while yes I could use iRulesLX for this, I decided to simply reformat the signatures in the data group to something more condusive to TCL iRules.
\n\n
Exporting and converting the tls-fingerprinting database to a BIG-IP external data group
\n\n
I don't really care about the \"id\" value, so I'll leave that out. And the \"desc\" field will be the value in the data group. The key will be the concatenation of all of the remaining fields.
\n\n
\n\"signature_data\" := \"ThunderBird (v38.0.1 OS X)\",
\n\n
I'm also going to remove the \"0x\" from the hex values, remove whitespace, and delimit each field with the plus (+) sign, so the resulting key for the above signature will look like this:
2. Convert it - use the following BASH script to extract each of the fields from each of the signatures. I should warn you now that my sed/awk/grep foo isn't strong, so I borrowed a BASH JSON parser from here: https://gist.github.com/cjus/1047794
\n\n
\n#!/bin/bash\n\nfunction jsonval () {\n ## description\n desc=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"desc\" |awk -F\": \" '{print $2}'`\n\n ## record_tls_version\n rect=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"record_tls_version\" |awk -F\": \" '{print $2}' |sed 's/0x//g'`\n if [ -z \"$rect\" ]; then rect=\"@@@@\"; fi\n\n ## tls_version\n tlsv=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"tls_version\" |awk -F\": \" '{print $2}' |sed 's/0x//g'`\n if [ -z \"$tlsv\" ]; then tlsv=\"@@@@\"; fi\n\n ## ciphersuite_length\n cipl=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"ciphersuite_length\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$cipl\" ]; then cipl=\"@@@@\"; fi\n\n ## ciphersuite\n ciph=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"ciphersuite\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$ciph\" ]; then tlsv=\"ciph\"; fi\n\n ## compression_length\n coml=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"compression_length\" |awk -F\": \" '{print $2}'`\n if [ -z \"$coml\" ]; then coml=\"@@@@\"; fi\n\n ## compression\n comp=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"compression\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$comp\" ]; then comp=\"@@@@\"; fi\n\n ## extensions\n exte=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"extensions\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$exte\" ]; then exte=\"@@@@\"; fi\n\n ## e_curves\n ecur=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"e_curves\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$ecur\" ]; then ecur=\"@@@@\"; fi\n\n ## sig_alg\n siga=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"sig_alg\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$siga\" ]; then siga=\"@@@@\"; fi\n\n ## ec_point_fmt\n ecfp=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"ec_point_fmt\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$ecfp\" ]; then ecfp=\"@@@@\"; fi\n\n echo \"\\\"$rect+$tlsv+$cipl+$ciph+$coml+$comp+$exte+$ecur+$siga+$ecfp\\\" := \\\"$desc\\\",\"\n}\n\nIFS=}\n\nfor i in `cat fingerprint.db`; do\n jsonval $i\ndone\n
\n\n
Create this BASH script however you like (VI, VIM, Joe, Nano, whatever), save it, chmod it so that it'll execute ('chmod 755 parser.sh'), and then run it ('./parser.sh'). It'll just echo the reformatted signatures to the screen, so you'll want to capture that as a file ('./parser.sh > fingerprint.dg'). It's also be a little slow, again due to my aggregious lack of sed/awk/grep (and regex) foo, but it should still finish in less than a minute.
\n\n
3. Import it - now go to the BIG-IP management UI, and under System - File Management - Data Group File List, click Import. Choose your reformatted text file, give it a meaningful name (ex. fingerprint_db), select \"String\" as the File Contents type, and use the same name (ex. fingerprint_db) in the Data Group Name field. On a v12 BIG-IP this will auto-create the local data group. On earlier systems you'll need to go manually create the local data group object that points to this external data group.
\n\n
\n\n
Creating a fingerprintTLS PROC iRule
\n\n
So now that we've reformatted and imported the fingerprintTLS database, let's build the iRule to parse out the data from the client's ClientHello. I should also warn you that this process requires a lot of binary manipulation, so please don't try to ingest it all at once if you're new to iRules. I'm building this iRule as a separate PROC that other data plane iRules can call. It won't be directly attached to a virtual server.
\n\n
\n## Library-Rule\n\n## TLS Fingerprint Procedure #################\n## \n## Author: Kevin Stewart, 12/2016\n## Derived from Lee Brotherston's \"tls-fingerprinting\" project @ https://github.com/LeeBrotherston/tls-fingerprinting\n## Purpose: to identify the user agent based on unique characteristics of the TLS ClientHello message\n## Input: \n## Full TCP payload collected in CLIENT_DATA event of a TLS handshake ClientHello message\n## Record length (rlen)\n## TLS outer version (outer)\n## TLS inner version (inner)\n## Client IP\n## Server IP\n##############################################\nproc fingerprintTLS { payload rlen outer inner clientip serverip } {\n \n ## The first 43 bytes of a ClientHello message are the record type, TLS versions, some length values and the\n ## handshake type. We should already know this stuff from the calling iRule. We're also going to be walking the\n ## packet, so the field_offset variable will be used to track where we are.\n set field_offset 43\n\n ## The first value in the payload after the offset is the session ID, which may be empty. Grab the session ID length\n ## value and move the field_offset variable that many bytes forward to skip it.\n binary scan ${payload} @${field_offset}c sessID_len\n set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]\n\n ## The next value in the payload is the ciphersuite list length (how big the ciphersuite list is. We need the binary\n ## and hex values of this data.\n binary scan ${payload} @${field_offset}S cipherList_len\n binary scan ${payload} @${field_offset}H4 cipherList_len_hex\n set cipherList_len_hex_text ${cipherList_len_hex}\n\n ## Now that we have the ciphersuite list length, let's offset the field_offset variable to skip over the length (2) bytes\n ## and go get the ciphersuite list. Multiple by 2 to get the number of appropriate hex characters.\n set field_offset [expr {${field_offset} + 2}]\n set cipherList_len_hex [expr {${cipherList_len} * 2}]\n binary scan ${payload} @${field_offset}H${cipherList_len_hex} cipherlist\n \n ## Next is the compression method length and compression method. First move field_offset to skip past the ciphersuite\n ## list, then grab the compression method length. Then move field_offset past the length (2) bytes and grab the \n ## compression method value. Finally, move field_offset past the compression method bytes.\n set field_offset [expr {${field_offset} + ${cipherList_len}}]\n binary scan ${payload} @${field_offset}c compression_len\n #set field_offset [expr {${field_offset} + ${compression_len}}]\n set field_offset [expr {${field_offset} + 1}]\n binary scan ${payload} @${field_offset}H[expr {${compression_len} * 2}] compression_type\n set field_offset [expr {${field_offset} + ${compression_len}}]\n \n ## We should be in the extensions section now, so we're going to just run through the remaining data and\n ## pick out the extensions as we go. But first let's make sure there's more record data left, based on \n ## the current field_offset vs. rlen.\n if { [expr {${field_offset} < ${rlen}}] } {\n ## There's extension data, so let's go get it. Skip the first 2 bytes that are the extensions length\n set field_offset [expr {${field_offset} + 2}]\n \n ## Make a variable to store the extension types we find\n set extensions_list \"\"\n \n ## Pad rlen by 1 byte\n set rlen [expr ${rlen} + 1]\n \n while { [expr {${field_offset} <= ${rlen}}] } {\n ## Grab the first 2 bytes to determine the extension type\n binary scan ${payload} @${field_offset}H4 ext\n\n ## Store the extension in the extensions_list variable\n append extensions_list ${ext}\n \n ## Increment field_offset past the 2 bytes of the extension type\n set field_offset [expr {${field_offset} + 2}]\n \n ## Grab the 2 bytes of extension lenth\n binary scan ${payload} @${field_offset}S ext_len\n \n ## Increment field_offset past the 2 bytes of the extension length\n set field_offset [expr {${field_offset} + 2}]\n \n ## Look for specific extension types in case these need to increment the field_offset (and because we need their values)\n switch $ext {\n \"000b\" {\n ## ec_point_format - there's another 1 byte after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 1}]H[expr {(${ext_len} - 1) * 2}] ext_data\n set ec_point_format ${ext_data}\n }\n \"000a\" {\n ## elliptic_curves - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data\n set elliptic_curves ${ext_data}\n }\n \"000d\" {\n ## sig_alg - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data\n set sig_alg ${ext_data}\n }\n default {\n ## Grab the otherwise unknown extension data\n binary scan ${payload} @${field_offset}H[expr {${ext_len} * 2}] ext_data\n }\n }\n \n ## Increment the field_offset past the extension data length. Repeat this loop until we reach rlen (the end of the payload)\n set field_offset [expr {${field_offset} + ${ext_len}}]\n }\n }\n \n ## Now let's compile all of that data.\n set cipl [string toupper ${cipherList_len_hex_text}]\n set ciph [string toupper ${cipherlist}]\n set coml ${compression_len}\n set comp [string toupper ${compression_type}]\n if { ( [info exists extensions_list] ) and ( ${extensions_list} ne \"\" ) } { set exte [string toupper ${extensions_list}] } else { set exte \"@@@@\" }\n if { ( [info exists elliptic_curves] ) and ( ${elliptic_curves} ne \"\" ) } { set ecur [string toupper ${elliptic_curves}] } else { set ecur \"@@@@\" }\n if { ( [info exists sig_alg] ) and ( ${sig_alg} ne \"\" ) } { set siga [string toupper ${sig_alg}] } else { set siga \"@@@@\" }\n if { ( [info exists ec_point_format] ) and ( ${ec_point_format} ne \"\" ) } { set ecfp [string toupper ${ec_point_format}] } else { set ecfp \"@@@@\" }\n\n ## Initialize the match variable\n set match \"\"\n \n ## Now let's build the fingerprint string and search the database\n set fingerprint_str \"${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}\"\n \n ## Un-comment this line to display the fingerprint string in the LTM log for troubleshooting\n ## log local0. \"${clientip}-${serverip}: fingerprint_str = ${fingerprint_str}\"\n\n if { [class match ${fingerprint_str} equals fingerprint_db] } {\n ## Direct match\n set match [class match -value ${fingerprint_str} equals fingerprint_db]\n } elseif { not ( ${ciph} starts_with \"C0\" ) and not ( ${ciph} starts_with \"00\" ) } {\n ## Hmm.. there's no direct match, which could either mean a database entry doesn't exist, or Chrome (and Opera) are adding\n ## special values to the cipherlist, extensions list and elliptic curves list.\n ## ex. 9A9A, 5A5A, EAEA, BABA, etc. at the beginning of the cipherlist \n ## Let's strip out these anomalous values and try the match again.\n \n ## Substract 2 bytes from cipherlist length\n set cipl [format %04x [expr [expr 0x${cipl}] - 2]]\n\n ## Subtract 2 bytes from the front of the cipher list\n set ciph [string range ${ciph} 4 end]\n \n ## Subtract 2 bytes from the front of the extensions list\n set exte [string range ${exte} 4 end]\n ## There might be an additional random set in the string that needs to be removed (pattern is \"(.)A\\1A\")\n regsub {(.)A\\1A} ${exte} \"\" exte\n ## If the above regsub doesn't work, try the following:\n #regsub {(\\wA)\\1} ${exte} \"\" exte\n\n ## Subtract 2 bytes from the front of the elliptic curves list\n set ecur [string range ${ecur} 4 end]\n \n ## Rebuild the fingerprint string\n set fingerprint_str \"${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}\"\n\n if { [class match ${fingerprint_str} equals fingerprint_db] } {\n ## Guess match\n set match [class match -value ${fingerprint_str} equals fingerprint_db]\n } else {\n ## No match\n set match \"\"\n }\n }\n \n ## Return the matching user agent string\n return ${match}\n}\n
\n\n
The PROC requires as input the full TCP payload (of the ClientHello message), the record length (extracted from the ClientHello message), the \"outer\" record TLS version and \"inner\" TLS version (also extracted form the ClientHello message). Using these values the PROC basically walks the payload looking for each of the required values (ciphersuite length, ciphersuite list, compression length, compression list, extensions list, elliptic curves, signature algorithms, and ec point formats). If any value doesn't exist in the payload (ex. the ClientHello doesn't contain a Sig_Alg field), that value is replaced with \"@@@@\". Once all of the values are found, the fingerprint string is created and used to search the data group. If there's a match, the user agent string (ex. ThunderBird (v38.0.1 OS X)) is returned to the caller. While testing this I noticed that newer versions of Chrome and Opera added what looked like \"markers\" to the ciphersuite list, extensions list, and elliptic curves list (ex. 9A9A, 5A5A, EAEA, BABA - always some alphanumeric value, followed by 'A', and repeated.). A cursory search didn't explain what these are, so maybe someone will know and report back. In the meantime, I added a \"guess\" function that removed these markers and tried the data group search again. All of the desktop browser testing (including Chrome and Opera) did get an accurate match with either the direct or guessed fingerprint, so I'll leave that in there until I find a better way to handle the markers.
\n\n
\n\n
Creating the caller iRule
\n\n
The only thing left to do is to create the caller iRule. This iRule only needs to detect an SSL/TLS ClientHello, and then pass that to the fingerprint PROC. This is just a stub iRule to show the proper implementation. Once you've determined a TCP packet is an SSL/TLS handshake ClientHello, call the PROC and then do something useful with the resulting user agent string, like switch the client SSL profile.
\n\n
\nwhen CLIENT_ACCEPTED {\n ## Collect the TCP payload\n TCP::collect\n}\nwhen CLIENT_DATA {\n ## Get the TLS packet type and versions\n if { ! [info exists rlen] } {\n binary scan [TCP::payload] cH4ScH6H4 rtype outer_sslver rlen hs_type rilen inner_sslver\n \n if { ( ${rtype} == 22 ) and ( ${hs_type} == 1 ) } {\n ## This is a TLS ClientHello message (22 = TLS handshake, 1 = ClientHello)\n \n ## Call the fingerprintTLS proc\n set fingerprint [call Library-Rule::fingerprintTLS [TCP::payload] ${rlen} ${outer_sslver} ${inner_sslver} [IP::client_addr] [IP::local_addr]]\n \n### Do Something here ###\n log local0. \"match = ${fingerprint}\"\n### Do Something here ###\n\n }\n }\n \n # Collect the rest of the record if necessary\n if { [TCP::payload length] < $rlen } {\n TCP::collect $rlen\n }\n \n ## Release the paylaod\n TCP::release\n}\n
\n\n
\n\n
What happens if there's no match?
\n\n
Yes, there are some caveats...
\n\n
It's safe to say that the tls-fingerprinting database isn't all inclusive. In fact it's FAR FROM COMPLETE and not always exact. I found, for example, that my version of Dropbox on a Win7 box (v16.4.30) doesn't make a match. It's nearly impossible to have the signature for every unique user agent every created, and all of the variations and versions of that agent. But what the database does have is the signatures for most browsers, so at the very least it makes for a nice way to whitelist browsers (vs. other agents). It also doesn't technically resolve the question in the original DC forum thread. The question was how to identify the device (ie. iOS, Android, Windows Phone), and for that you'd need some specific agent loaded on the device (not a browser) that could report that information. Mobile device management (MDM) solutions are particularly good at that sort of thing. The browser, Dropbox or other user agent on the mobile device may not specifically report the device (ex. \"for iOS\"). Some do, but I've found that most don't. At the end of this aticle I've included a few signatures that I found in my testing that aren't in the database. If your curious, uncomment the log local0. \"${clientip}-${serverip}: fingerprint_str = ${fingerprint_str}\" line in the fingerprintTLS PROC and then tail the LTM log. The caller iRule is already logging the returned user agent string, so if that is empty, you'll see the empty match in the log (match = ), preceded by the unmatched signature.
\n\n
Where this project may be most useful is in outbound traffic management, where you want to decrypt and inspect the Internet-bound traffic, but cannot decrypt some user agents becuase of things like cerificate pinning. Since the pinning decision happens at the client, the only other recourse is to bypass decryption and inspection based on the destination host name or IP address, which can be a tedious thing to manage. TLS fingerprinting might allow you to simply decrypt and inspect for the user agents that you know aren't affected by pinning, specifically browsers. You'll potentially miss some things that you could have decrypted, but you'll save yourself the burden of managing an ever-growing list of pinner exclusions.
\n\n
And on a final note, binary iRule manipulation is a very CPU-intensive thing to do. I could have very simply converted the raw payload to one long hex string (once) and walked that with string tools. I'll update the code when I have some time.
Hello, Kevin Stewart here. A while back someone asked an interesting question in the DevCentral forum about selecting a client SSL profile based on the device (ex. iOS, Android, Windows Phone). Normally you'd use a browser User-Agent HTTP header to identify the client user agent, but in this case, and based on the OSI model, you wouldn't be able to select an SSL profile (OSI layer 6) based on a User-Agent HTTP header (OSI layer 7), because at this point in time you don't yet have the layer 7 data - it's still encrypted. You could, however, use layer 3 or 4 data (IPs and ports), but that's generally not useful for identifying the client user agent. But there might still be a way...
\n\n
Lee Brotherston has discovered that during an SSL handshake, most client user agents (different browsers, Dropbox, Skype, etc.) will initiate an SSL handshake request in an ever-so-unique way. The ordered combination of TLS version, record TLS version, ciphersuites, compression options, list of extensions, elliptic curves and signature algorithms are all specific enough that you can actually build a signature based on that data, and the collection of signatures into a database. From that discovery Lee created a project called \"tls-fingerprinting\". Please check it out. Now certainly, a client's ClientHello could be modified to support different ciphersuites and other features, the same way you could spoof a User-Agent HTTP header. However this modification will often lower security by re-introducing previously unsupported options, or in many cases modification to the user agent's SSL parameters isn't easy or isn't possible.
\n\n
So given that we now have a new way to identify a user agent based on the client's ClientHello (the first message in the SSL handshake), I decided to re-visit the original DC forum request by integrating Lee's database into an iRules-based solution. The code example in the aforementioned thread just used the client's ciphersuite list, however, so today I'm going to expand on that and use all of the paramaters from the tls-fingerprinting database. Before we get started, I should mention two things:
\n\n
This article is going to be long (sorry about that), and
We're going to break it down into a few phases, specifically\n
Defining the values in the tls-fingerprinting signature
Exporting and converting the tls-fingerprinting database to a BIG-IP external data group
Creating a fingerprintTLS PROC iRule (name this \"Library-Rule\")
Creating the caller iRule
\n
\n\n
So let's get started.
\n\n
\n\n
Defining the values in the tls-fingerprinting signature
\n\n
Here's an example signature entry in Lee's tls-fingerprinting database (JSON version):
This is a pretty straight forward set of JSON key-value pairs. And if you're curious about what any of these values mean, I urge you to fire up Wireshark, open a browser to some HTTPS site, and then find a ClientHello message in the capture. You'll see all of these values and more, except for the first two, in that message. Our job then is to a) export the set of signatures to a BIG-IP external data group, and b) create an iRule that extracts all of these values from the client's ClientHello and compares those to the set of signatures in the data group. Of course iRules don't natively support JSON parsing, and while yes I could use iRulesLX for this, I decided to simply reformat the signatures in the data group to something more condusive to TCL iRules.
\n\n
Exporting and converting the tls-fingerprinting database to a BIG-IP external data group
\n\n
I don't really care about the \"id\" value, so I'll leave that out. And the \"desc\" field will be the value in the data group. The key will be the concatenation of all of the remaining fields.
\n\n
\n\"signature_data\" := \"ThunderBird (v38.0.1 OS X)\",
\n\n
I'm also going to remove the \"0x\" from the hex values, remove whitespace, and delimit each field with the plus (+) sign, so the resulting key for the above signature will look like this:
2. Convert it - use the following BASH script to extract each of the fields from each of the signatures. I should warn you now that my sed/awk/grep foo isn't strong, so I borrowed a BASH JSON parser from here: https://gist.github.com/cjus/1047794
\n\n
\n#!/bin/bash\n\nfunction jsonval () {\n ## description\n desc=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"desc\" |awk -F\": \" '{print $2}'`\n\n ## record_tls_version\n rect=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"record_tls_version\" |awk -F\": \" '{print $2}' |sed 's/0x//g'`\n if [ -z \"$rect\" ]; then rect=\"@@@@\"; fi\n\n ## tls_version\n tlsv=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"tls_version\" |awk -F\": \" '{print $2}' |sed 's/0x//g'`\n if [ -z \"$tlsv\" ]; then tlsv=\"@@@@\"; fi\n\n ## ciphersuite_length\n cipl=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"ciphersuite_length\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$cipl\" ]; then cipl=\"@@@@\"; fi\n\n ## ciphersuite\n ciph=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"ciphersuite\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$ciph\" ]; then tlsv=\"ciph\"; fi\n\n ## compression_length\n coml=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"compression_length\" |awk -F\": \" '{print $2}'`\n if [ -z \"$coml\" ]; then coml=\"@@@@\"; fi\n\n ## compression\n comp=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"compression\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$comp\" ]; then comp=\"@@@@\"; fi\n\n ## extensions\n exte=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"extensions\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$exte\" ]; then exte=\"@@@@\"; fi\n\n ## e_curves\n ecur=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"e_curves\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$ecur\" ]; then ecur=\"@@@@\"; fi\n\n ## sig_alg\n siga=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"sig_alg\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$siga\" ]; then siga=\"@@@@\"; fi\n\n ## ec_point_fmt\n ecfp=`echo $1 | sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' | grep -w \"ec_point_fmt\" |awk -F\": \" '{print $2}' |sed 's/0x//g' |sed 's/ //g'`\n if [ -z \"$ecfp\" ]; then ecfp=\"@@@@\"; fi\n\n echo \"\\\"$rect+$tlsv+$cipl+$ciph+$coml+$comp+$exte+$ecur+$siga+$ecfp\\\" := \\\"$desc\\\",\"\n}\n\nIFS=}\n\nfor i in `cat fingerprint.db`; do\n jsonval $i\ndone\n
\n\n
Create this BASH script however you like (VI, VIM, Joe, Nano, whatever), save it, chmod it so that it'll execute ('chmod 755 parser.sh'), and then run it ('./parser.sh'). It'll just echo the reformatted signatures to the screen, so you'll want to capture that as a file ('./parser.sh > fingerprint.dg'). It's also be a little slow, again due to my aggregious lack of sed/awk/grep (and regex) foo, but it should still finish in less than a minute.
\n\n
3. Import it - now go to the BIG-IP management UI, and under System - File Management - Data Group File List, click Import. Choose your reformatted text file, give it a meaningful name (ex. fingerprint_db), select \"String\" as the File Contents type, and use the same name (ex. fingerprint_db) in the Data Group Name field. On a v12 BIG-IP this will auto-create the local data group. On earlier systems you'll need to go manually create the local data group object that points to this external data group.
\n\n
\n\n
Creating a fingerprintTLS PROC iRule
\n\n
So now that we've reformatted and imported the fingerprintTLS database, let's build the iRule to parse out the data from the client's ClientHello. I should also warn you that this process requires a lot of binary manipulation, so please don't try to ingest it all at once if you're new to iRules. I'm building this iRule as a separate PROC that other data plane iRules can call. It won't be directly attached to a virtual server.
\n\n
\n## Library-Rule\n\n## TLS Fingerprint Procedure #################\n## \n## Author: Kevin Stewart, 12/2016\n## Derived from Lee Brotherston's \"tls-fingerprinting\" project @ https://github.com/LeeBrotherston/tls-fingerprinting\n## Purpose: to identify the user agent based on unique characteristics of the TLS ClientHello message\n## Input: \n## Full TCP payload collected in CLIENT_DATA event of a TLS handshake ClientHello message\n## Record length (rlen)\n## TLS outer version (outer)\n## TLS inner version (inner)\n## Client IP\n## Server IP\n##############################################\nproc fingerprintTLS { payload rlen outer inner clientip serverip } {\n \n ## The first 43 bytes of a ClientHello message are the record type, TLS versions, some length values and the\n ## handshake type. We should already know this stuff from the calling iRule. We're also going to be walking the\n ## packet, so the field_offset variable will be used to track where we are.\n set field_offset 43\n\n ## The first value in the payload after the offset is the session ID, which may be empty. Grab the session ID length\n ## value and move the field_offset variable that many bytes forward to skip it.\n binary scan ${payload} @${field_offset}c sessID_len\n set field_offset [expr {${field_offset} + 1 + ${sessID_len}}]\n\n ## The next value in the payload is the ciphersuite list length (how big the ciphersuite list is. We need the binary\n ## and hex values of this data.\n binary scan ${payload} @${field_offset}S cipherList_len\n binary scan ${payload} @${field_offset}H4 cipherList_len_hex\n set cipherList_len_hex_text ${cipherList_len_hex}\n\n ## Now that we have the ciphersuite list length, let's offset the field_offset variable to skip over the length (2) bytes\n ## and go get the ciphersuite list. Multiple by 2 to get the number of appropriate hex characters.\n set field_offset [expr {${field_offset} + 2}]\n set cipherList_len_hex [expr {${cipherList_len} * 2}]\n binary scan ${payload} @${field_offset}H${cipherList_len_hex} cipherlist\n \n ## Next is the compression method length and compression method. First move field_offset to skip past the ciphersuite\n ## list, then grab the compression method length. Then move field_offset past the length (2) bytes and grab the \n ## compression method value. Finally, move field_offset past the compression method bytes.\n set field_offset [expr {${field_offset} + ${cipherList_len}}]\n binary scan ${payload} @${field_offset}c compression_len\n #set field_offset [expr {${field_offset} + ${compression_len}}]\n set field_offset [expr {${field_offset} + 1}]\n binary scan ${payload} @${field_offset}H[expr {${compression_len} * 2}] compression_type\n set field_offset [expr {${field_offset} + ${compression_len}}]\n \n ## We should be in the extensions section now, so we're going to just run through the remaining data and\n ## pick out the extensions as we go. But first let's make sure there's more record data left, based on \n ## the current field_offset vs. rlen.\n if { [expr {${field_offset} < ${rlen}}] } {\n ## There's extension data, so let's go get it. Skip the first 2 bytes that are the extensions length\n set field_offset [expr {${field_offset} + 2}]\n \n ## Make a variable to store the extension types we find\n set extensions_list \"\"\n \n ## Pad rlen by 1 byte\n set rlen [expr ${rlen} + 1]\n \n while { [expr {${field_offset} <= ${rlen}}] } {\n ## Grab the first 2 bytes to determine the extension type\n binary scan ${payload} @${field_offset}H4 ext\n\n ## Store the extension in the extensions_list variable\n append extensions_list ${ext}\n \n ## Increment field_offset past the 2 bytes of the extension type\n set field_offset [expr {${field_offset} + 2}]\n \n ## Grab the 2 bytes of extension lenth\n binary scan ${payload} @${field_offset}S ext_len\n \n ## Increment field_offset past the 2 bytes of the extension length\n set field_offset [expr {${field_offset} + 2}]\n \n ## Look for specific extension types in case these need to increment the field_offset (and because we need their values)\n switch $ext {\n \"000b\" {\n ## ec_point_format - there's another 1 byte after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 1}]H[expr {(${ext_len} - 1) * 2}] ext_data\n set ec_point_format ${ext_data}\n }\n \"000a\" {\n ## elliptic_curves - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data\n set elliptic_curves ${ext_data}\n }\n \"000d\" {\n ## sig_alg - there's another 2 bytes after length\n ## Grab the extension data\n binary scan ${payload} @[expr {${field_offset} + 2}]H[expr {(${ext_len} - 2) * 2}] ext_data\n set sig_alg ${ext_data}\n }\n default {\n ## Grab the otherwise unknown extension data\n binary scan ${payload} @${field_offset}H[expr {${ext_len} * 2}] ext_data\n }\n }\n \n ## Increment the field_offset past the extension data length. Repeat this loop until we reach rlen (the end of the payload)\n set field_offset [expr {${field_offset} + ${ext_len}}]\n }\n }\n \n ## Now let's compile all of that data.\n set cipl [string toupper ${cipherList_len_hex_text}]\n set ciph [string toupper ${cipherlist}]\n set coml ${compression_len}\n set comp [string toupper ${compression_type}]\n if { ( [info exists extensions_list] ) and ( ${extensions_list} ne \"\" ) } { set exte [string toupper ${extensions_list}] } else { set exte \"@@@@\" }\n if { ( [info exists elliptic_curves] ) and ( ${elliptic_curves} ne \"\" ) } { set ecur [string toupper ${elliptic_curves}] } else { set ecur \"@@@@\" }\n if { ( [info exists sig_alg] ) and ( ${sig_alg} ne \"\" ) } { set siga [string toupper ${sig_alg}] } else { set siga \"@@@@\" }\n if { ( [info exists ec_point_format] ) and ( ${ec_point_format} ne \"\" ) } { set ecfp [string toupper ${ec_point_format}] } else { set ecfp \"@@@@\" }\n\n ## Initialize the match variable\n set match \"\"\n \n ## Now let's build the fingerprint string and search the database\n set fingerprint_str \"${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}\"\n \n ## Un-comment this line to display the fingerprint string in the LTM log for troubleshooting\n ## log local0. \"${clientip}-${serverip}: fingerprint_str = ${fingerprint_str}\"\n\n if { [class match ${fingerprint_str} equals fingerprint_db] } {\n ## Direct match\n set match [class match -value ${fingerprint_str} equals fingerprint_db]\n } elseif { not ( ${ciph} starts_with \"C0\" ) and not ( ${ciph} starts_with \"00\" ) } {\n ## Hmm.. there's no direct match, which could either mean a database entry doesn't exist, or Chrome (and Opera) are adding\n ## special values to the cipherlist, extensions list and elliptic curves list.\n ## ex. 9A9A, 5A5A, EAEA, BABA, etc. at the beginning of the cipherlist \n ## Let's strip out these anomalous values and try the match again.\n \n ## Substract 2 bytes from cipherlist length\n set cipl [format %04x [expr [expr 0x${cipl}] - 2]]\n\n ## Subtract 2 bytes from the front of the cipher list\n set ciph [string range ${ciph} 4 end]\n \n ## Subtract 2 bytes from the front of the extensions list\n set exte [string range ${exte} 4 end]\n ## There might be an additional random set in the string that needs to be removed (pattern is \"(.)A\\1A\")\n regsub {(.)A\\1A} ${exte} \"\" exte\n ## If the above regsub doesn't work, try the following:\n #regsub {(\\wA)\\1} ${exte} \"\" exte\n\n ## Subtract 2 bytes from the front of the elliptic curves list\n set ecur [string range ${ecur} 4 end]\n \n ## Rebuild the fingerprint string\n set fingerprint_str \"${outer}+${inner}+${cipl}+${ciph}+${coml}+${comp}+${exte}+${ecur}+${siga}+${ecfp}\"\n\n if { [class match ${fingerprint_str} equals fingerprint_db] } {\n ## Guess match\n set match [class match -value ${fingerprint_str} equals fingerprint_db]\n } else {\n ## No match\n set match \"\"\n }\n }\n \n ## Return the matching user agent string\n return ${match}\n}\n
\n\n
The PROC requires as input the full TCP payload (of the ClientHello message), the record length (extracted from the ClientHello message), the \"outer\" record TLS version and \"inner\" TLS version (also extracted form the ClientHello message). Using these values the PROC basically walks the payload looking for each of the required values (ciphersuite length, ciphersuite list, compression length, compression list, extensions list, elliptic curves, signature algorithms, and ec point formats). If any value doesn't exist in the payload (ex. the ClientHello doesn't contain a Sig_Alg field), that value is replaced with \"@@@@\". Once all of the values are found, the fingerprint string is created and used to search the data group. If there's a match, the user agent string (ex. ThunderBird (v38.0.1 OS X)) is returned to the caller. While testing this I noticed that newer versions of Chrome and Opera added what looked like \"markers\" to the ciphersuite list, extensions list, and elliptic curves list (ex. 9A9A, 5A5A, EAEA, BABA - always some alphanumeric value, followed by 'A', and repeated.). A cursory search didn't explain what these are, so maybe someone will know and report back. In the meantime, I added a \"guess\" function that removed these markers and tried the data group search again. All of the desktop browser testing (including Chrome and Opera) did get an accurate match with either the direct or guessed fingerprint, so I'll leave that in there until I find a better way to handle the markers.
\n\n
\n\n
Creating the caller iRule
\n\n
The only thing left to do is to create the caller iRule. This iRule only needs to detect an SSL/TLS ClientHello, and then pass that to the fingerprint PROC. This is just a stub iRule to show the proper implementation. Once you've determined a TCP packet is an SSL/TLS handshake ClientHello, call the PROC and then do something useful with the resulting user agent string, like switch the client SSL profile.
\n\n
\nwhen CLIENT_ACCEPTED {\n ## Collect the TCP payload\n TCP::collect\n}\nwhen CLIENT_DATA {\n ## Get the TLS packet type and versions\n if { ! [info exists rlen] } {\n binary scan [TCP::payload] cH4ScH6H4 rtype outer_sslver rlen hs_type rilen inner_sslver\n \n if { ( ${rtype} == 22 ) and ( ${hs_type} == 1 ) } {\n ## This is a TLS ClientHello message (22 = TLS handshake, 1 = ClientHello)\n \n ## Call the fingerprintTLS proc\n set fingerprint [call Library-Rule::fingerprintTLS [TCP::payload] ${rlen} ${outer_sslver} ${inner_sslver} [IP::client_addr] [IP::local_addr]]\n \n### Do Something here ###\n log local0. \"match = ${fingerprint}\"\n### Do Something here ###\n\n }\n }\n \n # Collect the rest of the record if necessary\n if { [TCP::payload length] < $rlen } {\n TCP::collect $rlen\n }\n \n ## Release the paylaod\n TCP::release\n}\n
\n\n
\n\n
What happens if there's no match?
\n\n
Yes, there are some caveats...
\n\n
It's safe to say that the tls-fingerprinting database isn't all inclusive. In fact it's FAR FROM COMPLETE and not always exact. I found, for example, that my version of Dropbox on a Win7 box (v16.4.30) doesn't make a match. It's nearly impossible to have the signature for every unique user agent every created, and all of the variations and versions of that agent. But what the database does have is the signatures for most browsers, so at the very least it makes for a nice way to whitelist browsers (vs. other agents). It also doesn't technically resolve the question in the original DC forum thread. The question was how to identify the device (ie. iOS, Android, Windows Phone), and for that you'd need some specific agent loaded on the device (not a browser) that could report that information. Mobile device management (MDM) solutions are particularly good at that sort of thing. The browser, Dropbox or other user agent on the mobile device may not specifically report the device (ex. \"for iOS\"). Some do, but I've found that most don't. At the end of this aticle I've included a few signatures that I found in my testing that aren't in the database. If your curious, uncomment the log local0. \"${clientip}-${serverip}: fingerprint_str = ${fingerprint_str}\" line in the fingerprintTLS PROC and then tail the LTM log. The caller iRule is already logging the returned user agent string, so if that is empty, you'll see the empty match in the log (match = ), preceded by the unmatched signature.
\n\n
Where this project may be most useful is in outbound traffic management, where you want to decrypt and inspect the Internet-bound traffic, but cannot decrypt some user agents becuase of things like cerificate pinning. Since the pinning decision happens at the client, the only other recourse is to bypass decryption and inspection based on the destination host name or IP address, which can be a tedious thing to manage. TLS fingerprinting might allow you to simply decrypt and inspect for the user agents that you know aren't affected by pinning, specifically browsers. You'll potentially miss some things that you could have decrypted, but you'll save yourself the burden of managing an ever-growing list of pinner exclusions.
\n\n
And on a final note, binary iRule manipulation is a very CPU-intensive thing to do. I could have very simply converted the raw payload to one long hex string (once) and walked that with string tools. I'll update the code when I have some time.
","kudosSumWeight":3,"postTime":"2016-12-30T05:05:00.000-08:00","images":{"__typename":"AssociatedImageConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"attachments":{"__typename":"AttachmentConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"tags":{"__typename":"TagConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDE","node":{"__typename":"Tag","id":"tag:application delivery","text":"application delivery","time":"2021-06-30T01:48:44.000-07:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDI","node":{"__typename":"Tag","id":"tag:BIG-IP","text":"BIG-IP","time":"2022-01-24T02:29:45.031-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDM","node":{"__typename":"Tag","id":"tag:iRules","text":"iRules","time":"2022-01-24T02:29:45.106-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDQ","node":{"__typename":"Tag","id":"tag:security","text":"security","time":"2009-07-03T08:19:36.000-07:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDU","node":{"__typename":"Tag","id":"tag:ssl","text":"ssl","time":"2022-01-24T02:29:52.690-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuMnwyLjF8b3wxMHxfTlZffDY","node":{"__typename":"Tag","id":"tag:tls","text":"tls","time":"2022-01-24T02:29:53.590-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}}]},"timeToRead":17,"rawTeaser":"","introduction":"","currentRevision":{"__ref":"Revision:revision:286251_1"},"latestVersion":{"__typename":"FriendlyVersion","major":"1","minor":"0"},"metrics":{"__typename":"MessageMetrics","views":10316},"visibilityScope":"PUBLIC","canonicalUrl":null,"seoTitle":null,"seoDescription":null,"placeholder":false,"originalMessageForPlaceholder":null,"contributors":{"__typename":"UserConnection","edges":[]},"nonCoAuthorContributors":{"__typename":"UserConnection","edges":[]},"coAuthors":{"__typename":"UserConnection","edges":[{"__typename":"UserEdge","node":{"__ref":"User:user:130391"}}]},"tkbMessagePolicies":{"__typename":"TkbMessagePolicies","canDoAuthoringActionsOnTkb":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.tkb.policy_can_do_authoring_action.accessDenied","key":"error.lithium.policies.tkb.policy_can_do_authoring_action.accessDenied","args":[]}}},"archivalData":null,"replies":{"__typename":"MessageConnection","edges":[{"__typename":"MessageEdge","cursor":"MjUuMnwyLjF8aXwxMHwzOToxfGludCwyODYyNTIsMjg2MjUy","node":{"__ref":"TkbReplyMessage:message:286252"}},{"__typename":"MessageEdge","cursor":"MjUuMnwyLjF8aXwxMHwzOToxfGludCwyODYyNTIsMjg2MjUz","node":{"__ref":"TkbReplyMessage:message:286253"}},{"__typename":"MessageEdge","cursor":"MjUuMnwyLjF8aXwxMHwzOToxfGludCwyODYyNTIsMjg2MjU0","node":{"__ref":"TkbReplyMessage:message:286254"}},{"__typename":"MessageEdge","cursor":"MjUuMnwyLjF8aXwxMHwzOToxfGludCwyODYyNTIsMjg2MjU1","node":{"__ref":"TkbReplyMessage:message:286255"}},{"__typename":"MessageEdge","cursor":"MjUuMnwyLjF8aXwxMHwzOToxfGludCwyODYyNTIsMjg2MjU2","node":{"__ref":"TkbReplyMessage:message:286256"}},{"__typename":"MessageEdge","cursor":"MjUuMnwyLjF8aXwxMHwzOToxfGludCwyODYyNTIsMjg2MjU3","node":{"__ref":"TkbReplyMessage:message:286257"}},{"__typename":"MessageEdge","cursor":"MjUuMnwyLjF8aXwxMHwzOToxfGludCwyODYyNTIsMjg2MjU4","node":{"__ref":"TkbReplyMessage:message:286258"}},{"__typename":"MessageEdge","cursor":"MjUuMnwyLjF8aXwxMHwzOToxfGludCwyODYyNTIsMjg2MjU5","node":{"__ref":"TkbReplyMessage:message:286259"}}],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"customFields":[],"revisions({\"constraints\":{\"isPublished\":{\"eq\":true}},\"first\":1})":{"__typename":"RevisionConnection","totalCount":1}},"Conversation:conversation:286251":{"__typename":"Conversation","id":"conversation:286251","solved":false,"topic":{"__ref":"TkbTopicMessage:message:286251"},"lastPostingActivityTime":"2021-03-22T17:14:59.000-07:00","lastPostTime":"2021-03-22T17:14:59.000-07:00","unreadReplyCount":8,"isSubscribed":false},"ModerationData:moderation_data:286251":{"__typename":"ModerationData","id":"moderation_data:286251","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"Revision:revision:286251_1":{"__typename":"Revision","id":"revision:286251_1","lastEditTime":"2016-12-30T05:05:00.000-08:00"},"CachedAsset:theme:customTheme1-1742462581110":{"__typename":"CachedAsset","id":"theme:customTheme1-1742462581110","value":{"id":"customTheme1","animation":{"fast":"150ms","normal":"250ms","slow":"500ms","slowest":"750ms","function":"cubic-bezier(0.07, 0.91, 0.51, 1)","__typename":"AnimationThemeSettings"},"avatar":{"borderRadius":"50%","collections":["custom"],"__typename":"AvatarThemeSettings"},"basics":{"browserIcon":{"imageAssetName":"JimmyPackets-512-1702592938213.png","imageLastModified":"1702592945815","__typename":"ThemeAsset"},"customerLogo":{"imageAssetName":"f5_logo_fix-1704824537976.svg","imageLastModified":"1704824540697","__typename":"ThemeAsset"},"maximumWidthOfPageContent":"1600px","oneColumnNarrowWidth":"800px","gridGutterWidthMd":"30px","gridGutterWidthXs":"10px","pageWidthStyle":"WIDTH_OF_PAGE_CONTENT","__typename":"BasicsThemeSettings"},"buttons":{"borderRadiusSm":"5px","borderRadius":"5px","borderRadiusLg":"5px","paddingY":"5px","paddingYLg":"7px","paddingYHero":"var(--lia-bs-btn-padding-y-lg)","paddingX":"12px","paddingXLg":"14px","paddingXHero":"42px","fontStyle":"NORMAL","fontWeight":"400","textTransform":"NONE","disabledOpacity":0.5,"primaryTextColor":"var(--lia-bs-white)","primaryTextHoverColor":"var(--lia-bs-white)","primaryTextActiveColor":"var(--lia-bs-white)","primaryBgColor":"var(--lia-bs-primary)","primaryBgHoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.85))","primaryBgActiveColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.7))","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","primaryBorderActive":"1px solid transparent","primaryBorderFocus":"1px solid var(--lia-bs-white)","primaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","secondaryTextColor":"var(--lia-bs-gray-900)","secondaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","secondaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","secondaryBgColor":"var(--lia-bs-gray-400)","secondaryBgHoverColor":"hsl(var(--lia-bs-gray-400-h), var(--lia-bs-gray-400-s), calc(var(--lia-bs-gray-400-l) * 0.96))","secondaryBgActiveColor":"hsl(var(--lia-bs-gray-400-h), var(--lia-bs-gray-400-s), calc(var(--lia-bs-gray-400-l) * 0.92))","secondaryBorder":"1px solid transparent","secondaryBorderHover":"1px solid transparent","secondaryBorderActive":"1px solid transparent","secondaryBorderFocus":"1px solid transparent","secondaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","tertiaryTextColor":"var(--lia-bs-gray-900)","tertiaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","tertiaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","tertiaryBgColor":"transparent","tertiaryBgHoverColor":"transparent","tertiaryBgActiveColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.04)","tertiaryBorder":"1px solid transparent","tertiaryBorderHover":"1px solid hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","tertiaryBorderActive":"1px solid transparent","tertiaryBorderFocus":"1px solid transparent","tertiaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","destructiveTextColor":"var(--lia-bs-danger)","destructiveTextHoverColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.95))","destructiveTextActiveColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.9))","destructiveBgColor":"var(--lia-bs-gray-300)","destructiveBgHoverColor":"hsl(var(--lia-bs-gray-300-h), var(--lia-bs-gray-300-s), calc(var(--lia-bs-gray-300-l) * 0.96))","destructiveBgActiveColor":"hsl(var(--lia-bs-gray-300-h), var(--lia-bs-gray-300-s), calc(var(--lia-bs-gray-300-l) * 0.92))","destructiveBorder":"1px solid transparent","destructiveBorderHover":"1px solid transparent","destructiveBorderActive":"1px solid transparent","destructiveBorderFocus":"1px solid transparent","destructiveBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","__typename":"ButtonsThemeSettings"},"border":{"color":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","mainContent":"NONE","sideContent":"NONE","radiusSm":"3px","radius":"5px","radiusLg":"9px","radius50":"100vw","__typename":"BorderThemeSettings"},"boxShadow":{"xs":"0 0 0 1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08), 0 3px 0 -1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08)","sm":"0 2px 4px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.06)","md":"0 5px 15px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.15)","lg":"0 10px 30px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.15)","__typename":"BoxShadowThemeSettings"},"cards":{"bgColor":"var(--lia-panel-bg-color)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":"var(--lia-box-shadow-xs)","__typename":"CardsThemeSettings"},"chip":{"maxWidth":"300px","height":"30px","__typename":"ChipThemeSettings"},"coreTypes":{"defaultMessageLinkColor":"var(--lia-bs-primary)","defaultMessageLinkDecoration":"none","defaultMessageLinkFontStyle":"NORMAL","defaultMessageLinkFontWeight":"400","defaultMessageFontStyle":"NORMAL","defaultMessageFontWeight":"400","forumColor":"#0C5C8D","forumFontFamily":"var(--lia-bs-font-family-base)","forumFontWeight":"var(--lia-default-message-font-weight)","forumLineHeight":"var(--lia-bs-line-height-base)","forumFontStyle":"var(--lia-default-message-font-style)","forumMessageLinkColor":"var(--lia-default-message-link-color)","forumMessageLinkDecoration":"var(--lia-default-message-link-decoration)","forumMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","forumMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","forumSolvedColor":"#62C026","blogColor":"#730015","blogFontFamily":"var(--lia-bs-font-family-base)","blogFontWeight":"var(--lia-default-message-font-weight)","blogLineHeight":"1.75","blogFontStyle":"var(--lia-default-message-font-style)","blogMessageLinkColor":"var(--lia-default-message-link-color)","blogMessageLinkDecoration":"var(--lia-default-message-link-decoration)","blogMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","blogMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","tkbColor":"#C20025","tkbFontFamily":"var(--lia-bs-font-family-base)","tkbFontWeight":"var(--lia-default-message-font-weight)","tkbLineHeight":"1.75","tkbFontStyle":"var(--lia-default-message-font-style)","tkbMessageLinkColor":"var(--lia-default-message-link-color)","tkbMessageLinkDecoration":"var(--lia-default-message-link-decoration)","tkbMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","tkbMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaColor":"#4099E2","qandaFontFamily":"var(--lia-bs-font-family-base)","qandaFontWeight":"var(--lia-default-message-font-weight)","qandaLineHeight":"var(--lia-bs-line-height-base)","qandaFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkColor":"var(--lia-default-message-link-color)","qandaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","qandaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaSolvedColor":"#3FA023","ideaColor":"#F3704B","ideaFontFamily":"var(--lia-bs-font-family-base)","ideaFontWeight":"var(--lia-default-message-font-weight)","ideaLineHeight":"var(--lia-bs-line-height-base)","ideaFontStyle":"var(--lia-default-message-font-style)","ideaMessageLinkColor":"var(--lia-default-message-link-color)","ideaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","ideaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","ideaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","contestColor":"#FCC845","contestFontFamily":"var(--lia-bs-font-family-base)","contestFontWeight":"var(--lia-default-message-font-weight)","contestLineHeight":"var(--lia-bs-line-height-base)","contestFontStyle":"var(--lia-default-message-link-font-style)","contestMessageLinkColor":"var(--lia-default-message-link-color)","contestMessageLinkDecoration":"var(--lia-default-message-link-decoration)","contestMessageLinkFontStyle":"ITALIC","contestMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","occasionColor":"#EE4B5B","occasionFontFamily":"var(--lia-bs-font-family-base)","occasionFontWeight":"var(--lia-default-message-font-weight)","occasionLineHeight":"var(--lia-bs-line-height-base)","occasionFontStyle":"var(--lia-default-message-font-style)","occasionMessageLinkColor":"var(--lia-default-message-link-color)","occasionMessageLinkDecoration":"var(--lia-default-message-link-decoration)","occasionMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","occasionMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","grouphubColor":"#491B62","categoryColor":"#949494","communityColor":"#FFFFFF","productColor":"#949494","__typename":"CoreTypesThemeSettings"},"colors":{"black":"#000000","white":"#FFFFFF","gray100":"#F7F7F7","gray200":"#F7F7F7","gray300":"#E8E8E8","gray400":"#D9D9D9","gray500":"#CCCCCC","gray600":"#949494","gray700":"#707070","gray800":"#545454","gray900":"#333333","dark":"#545454","light":"#F7F7F7","primary":"#0C5C8D","secondary":"#333333","bodyText":"#222222","bodyBg":"#F5F5F5","info":"#1D9CD3","success":"#62C026","warning":"#FFD651","danger":"#C20025","alertSystem":"#FF6600","textMuted":"#707070","highlight":"#FFFCAD","outline":"var(--lia-bs-primary)","custom":["#C20025","#081B85","#009639","#B3C6D7","#7CC0EB","#F29A36"],"__typename":"ColorsThemeSettings"},"divider":{"size":"3px","marginLeft":"4px","marginRight":"4px","borderRadius":"50%","bgColor":"var(--lia-bs-gray-600)","bgColorActive":"var(--lia-bs-gray-600)","__typename":"DividerThemeSettings"},"dropdown":{"fontSize":"var(--lia-bs-font-size-sm)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius-sm)","dividerBg":"var(--lia-bs-gray-300)","itemPaddingY":"5px","itemPaddingX":"20px","headerColor":"var(--lia-bs-gray-700)","__typename":"DropdownThemeSettings"},"email":{"link":{"color":"#0069D4","hoverColor":"#0061c2","decoration":"none","hoverDecoration":"underline","__typename":"EmailLinkSettings"},"border":{"color":"#e4e4e4","__typename":"EmailBorderSettings"},"buttons":{"borderRadiusLg":"5px","paddingXLg":"16px","paddingYLg":"7px","fontWeight":"700","primaryTextColor":"#ffffff","primaryTextHoverColor":"#ffffff","primaryBgColor":"#0069D4","primaryBgHoverColor":"#005cb8","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","__typename":"EmailButtonsSettings"},"panel":{"borderRadius":"5px","borderColor":"#e4e4e4","__typename":"EmailPanelSettings"},"__typename":"EmailThemeSettings"},"emoji":{"skinToneDefault":"#ffcd43","skinToneLight":"#fae3c5","skinToneMediumLight":"#e2cfa5","skinToneMedium":"#daa478","skinToneMediumDark":"#a78058","skinToneDark":"#5e4d43","__typename":"EmojiThemeSettings"},"heading":{"color":"var(--lia-bs-body-color)","fontFamily":"Inter","fontStyle":"NORMAL","fontWeight":"600","h1FontSize":"30px","h2FontSize":"25px","h3FontSize":"20px","h4FontSize":"18px","h5FontSize":"16px","h6FontSize":"16px","lineHeight":"1.2","subHeaderFontSize":"11px","subHeaderFontWeight":"500","h1LetterSpacing":"normal","h2LetterSpacing":"normal","h3LetterSpacing":"normal","h4LetterSpacing":"normal","h5LetterSpacing":"normal","h6LetterSpacing":"normal","subHeaderLetterSpacing":"2px","h1FontWeight":"var(--lia-bs-headings-font-weight)","h2FontWeight":"var(--lia-bs-headings-font-weight)","h3FontWeight":"var(--lia-bs-headings-font-weight)","h4FontWeight":"var(--lia-bs-headings-font-weight)","h5FontWeight":"var(--lia-bs-headings-font-weight)","h6FontWeight":"var(--lia-bs-headings-font-weight)","__typename":"HeadingThemeSettings"},"icons":{"size10":"10px","size12":"12px","size14":"14px","size16":"16px","size20":"20px","size24":"24px","size30":"30px","size40":"40px","size50":"50px","size60":"60px","size80":"80px","size120":"120px","size160":"160px","__typename":"IconsThemeSettings"},"imagePreview":{"bgColor":"var(--lia-bs-gray-900)","titleColor":"var(--lia-bs-white)","controlColor":"var(--lia-bs-white)","controlBgColor":"var(--lia-bs-gray-800)","__typename":"ImagePreviewThemeSettings"},"input":{"borderColor":"var(--lia-bs-gray-600)","disabledColor":"var(--lia-bs-gray-600)","focusBorderColor":"var(--lia-bs-primary)","labelMarginBottom":"10px","btnFontSize":"var(--lia-bs-font-size-sm)","focusBoxShadow":"0 0 0 3px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","checkLabelMarginBottom":"2px","checkboxBorderRadius":"3px","borderRadiusSm":"var(--lia-bs-border-radius-sm)","borderRadius":"var(--lia-bs-border-radius)","borderRadiusLg":"var(--lia-bs-border-radius-lg)","formTextMarginTop":"4px","textAreaBorderRadius":"var(--lia-bs-border-radius)","activeFillColor":"var(--lia-bs-primary)","__typename":"InputThemeSettings"},"loading":{"dotDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.2)","dotLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.5)","barDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.06)","barLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.4)","__typename":"LoadingThemeSettings"},"link":{"color":"var(--lia-bs-primary)","hoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) - 10%))","decoration":"none","hoverDecoration":"underline","__typename":"LinkThemeSettings"},"listGroup":{"itemPaddingY":"15px","itemPaddingX":"15px","borderColor":"var(--lia-bs-gray-300)","__typename":"ListGroupThemeSettings"},"modal":{"contentTextColor":"var(--lia-bs-body-color)","contentBg":"var(--lia-bs-white)","backgroundBg":"var(--lia-bs-black)","smSize":"440px","mdSize":"760px","lgSize":"1080px","backdropOpacity":0.3,"contentBoxShadowXs":"var(--lia-bs-box-shadow-sm)","contentBoxShadow":"var(--lia-bs-box-shadow)","headerFontWeight":"700","__typename":"ModalThemeSettings"},"navbar":{"position":"FIXED","background":{"attachment":null,"clip":null,"color":"var(--lia-bs-white)","imageAssetName":null,"imageLastModified":"0","origin":null,"position":"CENTER_CENTER","repeat":"NO_REPEAT","size":"COVER","__typename":"BackgroundProps"},"backgroundOpacity":0.8,"paddingTop":"15px","paddingBottom":"15px","borderBottom":"1px solid var(--lia-bs-border-color)","boxShadow":"var(--lia-bs-box-shadow-sm)","brandMarginRight":"30px","brandMarginRightSm":"10px","brandLogoHeight":"30px","linkGap":"10px","linkJustifyContent":"flex-start","linkPaddingY":"5px","linkPaddingX":"10px","linkDropdownPaddingY":"9px","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkColor":"var(--lia-bs-body-color)","linkHoverColor":"var(--lia-bs-primary)","linkFontSize":"var(--lia-bs-font-size-sm)","linkFontStyle":"NORMAL","linkFontWeight":"400","linkTextTransform":"NONE","linkLetterSpacing":"normal","linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkBgColor":"transparent","linkBgHoverColor":"transparent","linkBorder":"none","linkBorderHover":"none","linkBoxShadow":"none","linkBoxShadowHover":"none","linkTextBorderBottom":"none","linkTextBorderBottomHover":"none","dropdownPaddingTop":"10px","dropdownPaddingBottom":"15px","dropdownPaddingX":"10px","dropdownMenuOffset":"2px","dropdownDividerMarginTop":"10px","dropdownDividerMarginBottom":"10px","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","controllerIconColor":"var(--lia-bs-body-color)","controllerIconHoverColor":"var(--lia-bs-body-color)","controllerTextColor":"var(--lia-nav-controller-icon-color)","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","controllerHighlightColor":"hsla(30, 100%, 50%)","controllerHighlightTextColor":"var(--lia-yiq-light)","controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerColor":"var(--lia-nav-controller-icon-color)","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","hamburgerBgColor":"transparent","hamburgerBgHoverColor":"transparent","hamburgerBorder":"none","hamburgerBorderHover":"none","collapseMenuMarginLeft":"20px","collapseMenuDividerBg":"var(--lia-nav-link-color)","collapseMenuDividerOpacity":0.16,"__typename":"NavbarThemeSettings"},"pager":{"textColor":"var(--lia-bs-link-color)","textFontWeight":"var(--lia-font-weight-md)","textFontSize":"var(--lia-bs-font-size-sm)","__typename":"PagerThemeSettings"},"panel":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-bs-border-radius)","borderColor":"var(--lia-bs-border-color)","boxShadow":"none","__typename":"PanelThemeSettings"},"popover":{"arrowHeight":"8px","arrowWidth":"16px","maxWidth":"300px","minWidth":"100px","headerBg":"var(--lia-bs-white)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius)","boxShadow":"0 0.5rem 1rem hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.15)","__typename":"PopoverThemeSettings"},"prism":{"color":"#000000","bgColor":"#f5f2f0","fontFamily":"var(--font-family-monospace)","fontSize":"var(--lia-bs-font-size-base)","fontWeightBold":"var(--lia-bs-font-weight-bold)","fontStyleItalic":"italic","tabSize":2,"highlightColor":"#b3d4fc","commentColor":"#62707e","punctuationColor":"#6f6f6f","namespaceOpacity":"0.7","propColor":"#990055","selectorColor":"#517a00","operatorColor":"#906736","operatorBgColor":"hsla(0, 0%, 100%, 0.5)","keywordColor":"#0076a9","functionColor":"#d3284b","variableColor":"#c14700","__typename":"PrismThemeSettings"},"rte":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":" var(--lia-panel-box-shadow)","customColor1":"#bfedd2","customColor2":"#fbeeb8","customColor3":"#f8cac6","customColor4":"#eccafa","customColor5":"#c2e0f4","customColor6":"#2dc26b","customColor7":"#f1c40f","customColor8":"#e03e2d","customColor9":"#b96ad9","customColor10":"#3598db","customColor11":"#169179","customColor12":"#e67e23","customColor13":"#ba372a","customColor14":"#843fa1","customColor15":"#236fa1","customColor16":"#ecf0f1","customColor17":"#ced4d9","customColor18":"#95a5a6","customColor19":"#7e8c8d","customColor20":"#34495e","customColor21":"#000000","customColor22":"#ffffff","defaultMessageHeaderMarginTop":"14px","defaultMessageHeaderMarginBottom":"10px","defaultMessageItemMarginTop":"0","defaultMessageItemMarginBottom":"10px","diffAddedColor":"hsla(170, 53%, 51%, 0.4)","diffChangedColor":"hsla(43, 97%, 63%, 0.4)","diffNoneColor":"hsla(0, 0%, 80%, 0.4)","diffRemovedColor":"hsla(9, 74%, 47%, 0.4)","specialMessageHeaderMarginTop":"14px","specialMessageHeaderMarginBottom":"10px","specialMessageItemMarginTop":"0","specialMessageItemMarginBottom":"10px","__typename":"RteThemeSettings"},"tags":{"bgColor":"var(--lia-bs-gray-200)","bgHoverColor":"var(--lia-bs-gray-400)","borderRadius":"var(--lia-bs-border-radius-sm)","color":"var(--lia-bs-body-color)","hoverColor":"var(--lia-bs-body-color)","fontWeight":"var(--lia-font-weight-md)","fontSize":"var(--lia-font-size-xxs)","textTransform":"UPPERCASE","letterSpacing":"0.5px","__typename":"TagsThemeSettings"},"toasts":{"borderRadius":"var(--lia-bs-border-radius)","paddingX":"12px","__typename":"ToastsThemeSettings"},"typography":{"fontFamilyBase":"Atkinson Hyperlegible","fontStyleBase":"NORMAL","fontWeightBase":"400","fontWeightLight":"300","fontWeightNormal":"400","fontWeightMd":"500","fontWeightBold":"700","letterSpacingSm":"normal","letterSpacingXs":"normal","lineHeightBase":"1.3","fontSizeBase":"15px","fontSizeXxs":"11px","fontSizeXs":"12px","fontSizeSm":"13px","fontSizeLg":"20px","fontSizeXl":"24px","smallFontSize":"14px","customFonts":[],"__typename":"TypographyThemeSettings"},"unstyledListItem":{"marginBottomSm":"5px","marginBottomMd":"10px","marginBottomLg":"15px","marginBottomXl":"20px","marginBottomXxl":"25px","__typename":"UnstyledListItemThemeSettings"},"yiq":{"light":"#ffffff","dark":"#000000","__typename":"YiqThemeSettings"},"colorLightness":{"primaryDark":0.36,"primaryLight":0.74,"primaryLighter":0.89,"primaryLightest":0.95,"infoDark":0.39,"infoLight":0.72,"infoLighter":0.85,"infoLightest":0.93,"successDark":0.24,"successLight":0.62,"successLighter":0.8,"successLightest":0.91,"warningDark":0.39,"warningLight":0.68,"warningLighter":0.84,"warningLightest":0.93,"dangerDark":0.41,"dangerLight":0.72,"dangerLighter":0.89,"dangerLightest":0.95,"__typename":"ColorLightnessThemeSettings"},"localOverride":false,"__typename":"Theme"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-1743097588266","value":{"title":"Loading..."},"localOverride":false},"CachedAsset:quilt:f5.prod:pages/kbs/TkbMessagePage:board:TechnicalArticles-1743097590074":{"__typename":"CachedAsset","id":"quilt:f5.prod:pages/kbs/TkbMessagePage:board:TechnicalArticles-1743097590074","value":{"id":"TkbMessagePage","container":{"id":"Common","headerProps":{"backgroundImageProps":null,"backgroundColor":null,"addComponents":null,"removeComponents":["community.widget.bannerWidget"],"componentOrder":null,"__typename":"QuiltContainerSectionProps"},"headerComponentProps":{"community.widget.breadcrumbWidget":{"disableLastCrumbForDesktop":false}},"footerProps":null,"footerComponentProps":null,"items":[{"id":"message-list","layout":"MAIN_SIDE","bgColor":"transparent","showTitle":true,"showDescription":true,"textPosition":"CENTER","textColor":"var(--lia-bs-body-color)","sectionEditLevel":null,"bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"MainSideQuiltSection","columnMap":{"main":[{"id":"tkbs.widget.tkbArticleWidget","className":"lia-tkb-container","props":{"contributorListType":"panel","showHelpfulness":false,"showTimestamp":true,"showGuideNavigationSection":true,"showVersion":true,"lazyLoad":false,"editLevel":"CONFIGURE"},"__typename":"QuiltComponent"}],"side":[{"id":"featuredWidgets.widget.featuredContentWidget","className":null,"props":{"instanceId":"featuredWidgets.widget.featuredContentWidget-1702666556326","layoutProps":{"layout":"card","layoutOptions":{"useRepliesCount":false,"useAuthorRank":false,"useTimeToRead":true,"useKudosCount":false,"useViewCount":true,"usePreviewMedia":true,"useBody":false,"useCenteredCardContent":false,"useTags":true,"useTimestamp":false,"useBoardLink":true,"useAuthorLink":false,"useSolvedBadge":true}},"titleSrOnly":false,"showPager":true,"pageSize":3,"lazyLoad":true},"__typename":"QuiltComponent"},{"id":"messages.widget.relatedContentWidget","className":null,"props":{"hideIfEmpty":true,"enablePagination":true,"useTitle":true,"listVariant":{"type":"listGroup"},"pageSize":3,"style":"list","pagerVariant":{"type":"loadMore"},"viewVariant":{"type":"inline","props":{"useRepliesCount":true,"useMedia":true,"useAuthorRank":false,"useNode":true,"useTimeToRead":true,"useSpoilerFreeBody":true,"useKudosCount":true,"useNodeLink":true,"useViewCount":true,"usePreviewMedia":false,"useBody":false,"timeStampType":"postTime","useTags":true,"clampSubjectLines":2,"useBoardIcon":false,"useMessageTimeLink":true,"clampBodyLines":3,"useTextBody":true,"useSolvedBadge":true,"useAvatar":true,"useAuthorLogin":true,"useUnreadCount":true}},"lazyLoad":true,"panelType":"divider"},"__typename":"QuiltComponent"}],"__typename":"MainSideSectionColumns"}}],"__typename":"QuiltContainer"},"__typename":"Quilt","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/EmailVerification-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/common/EmailVerification-1743097588266","value":{"email.verification.title":"Email Verification Required","email.verification.message.update.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. To change your email, visit My Settings.","email.verification.message.resend.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. Resend email."},"localOverride":false},"CachedAsset:text:en_US-pages/kbs/TkbMessagePage-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-pages/kbs/TkbMessagePage-1743097588266","value":{"title":"{contextMessageSubject} | {communityTitle}","errorMissing":"This article cannot be found","name":"TKB Message Page","section.message-list.title":"","archivedMessageTitle":"This Content Has Been Archived","section.erPqcf.title":"","section.erPqcf.description":"","section.message-list.description":""},"localOverride":false},"CachedAsset:quiltWrapper:f5.prod:Common:1742462479161":{"__typename":"CachedAsset","id":"quiltWrapper:f5.prod:Common:1742462479161","value":{"id":"Common","header":{"backgroundImageProps":{"assetName":"header.jpg","backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"LEFT_CENTER","lastModified":"1702932449000","__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"custom.widget.Beta_MetaNav","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"community.widget.navbarWidget","props":{"showUserName":false,"showRegisterLink":true,"style":{"boxShadow":"var(--lia-bs-box-shadow-sm)","linkFontWeight":"700","controllerHighlightColor":"hsla(30, 100%, 50%)","dropdownDividerMarginBottom":"10px","hamburgerBorderHover":"none","linkFontSize":"15px","linkBoxShadowHover":"none","backgroundOpacity":0.4,"controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerBgColor":"transparent","linkTextBorderBottom":"none","hamburgerColor":"var(--lia-nav-controller-icon-color)","brandLogoHeight":"48px","linkLetterSpacing":"normal","linkBgHoverColor":"transparent","collapseMenuDividerOpacity":0.16,"paddingBottom":"10px","dropdownPaddingBottom":"15px","dropdownMenuOffset":"2px","hamburgerBgHoverColor":"transparent","borderBottom":"0","hamburgerBorder":"none","dropdownPaddingX":"10px","brandMarginRightSm":"10px","linkBoxShadow":"none","linkJustifyContent":"center","linkColor":"var(--lia-bs-primary)","collapseMenuDividerBg":"var(--lia-nav-link-color)","dropdownPaddingTop":"10px","controllerHighlightTextColor":"var(--lia-yiq-dark)","background":{"imageAssetName":"","color":"var(--lia-bs-white)","size":"COVER","repeat":"NO_REPEAT","position":"CENTER_CENTER","imageLastModified":""},"linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkHoverColor":"var(--lia-bs-primary)","position":"FIXED","linkBorder":"none","linkTextBorderBottomHover":"2px solid #0C5C8D","brandMarginRight":"30px","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","linkBorderHover":"none","collapseMenuMarginLeft":"20px","linkFontStyle":"NORMAL","linkPaddingX":"10px","paddingTop":"10px","linkPaddingY":"5px","linkTextTransform":"NONE","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkBgColor":"transparent","linkDropdownPaddingY":"9px","controllerIconColor":"#0C5C8D","dropdownDividerMarginTop":"10px","linkGap":"10px","controllerIconHoverColor":"#0C5C8D"},"links":{"sideLinks":[],"mainLinks":[{"children":[{"linkType":"INTERNAL","id":"migrated-link-1","params":{"boardId":"TechnicalForum","categoryId":"Forums"},"routeName":"ForumBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-2","params":{"boardId":"WaterCooler","categoryId":"Forums"},"routeName":"ForumBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-0","params":{"categoryId":"Forums"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-4","params":{"boardId":"codeshare","categoryId":"CrowdSRC"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-5","params":{"boardId":"communityarticles","categoryId":"CrowdSRC"},"routeName":"TkbBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-3","params":{"categoryId":"CrowdSRC"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-7","params":{"boardId":"TechnicalArticles","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"article-series","params":{"boardId":"article-series","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"security-insights","params":{"boardId":"security-insights","categoryId":"Articles"},"routeName":"TkbBoardPage"},{"linkType":"INTERNAL","id":"migrated-link-8","params":{"boardId":"DevCentralNews","categoryId":"Articles"},"routeName":"TkbBoardPage"}],"linkType":"INTERNAL","id":"migrated-link-6","params":{"categoryId":"Articles"},"routeName":"CategoryPage"},{"children":[{"linkType":"INTERNAL","id":"migrated-link-10","params":{"categoryId":"CommunityGroups"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"migrated-link-11","params":{"categoryId":"F5-Groups"},"routeName":"CategoryPage"}],"linkType":"INTERNAL","id":"migrated-link-9","params":{"categoryId":"GroupsCategory"},"routeName":"CategoryPage"},{"children":[],"linkType":"INTERNAL","id":"migrated-link-12","params":{"boardId":"Events","categoryId":"top"},"routeName":"EventBoardPage"},{"children":[],"linkType":"INTERNAL","id":"migrated-link-13","params":{"boardId":"Suggestions","categoryId":"top"},"routeName":"IdeaBoardPage"},{"children":[],"linkType":"EXTERNAL","id":"Common-external-link","url":"https://community.f5.com/c/how-do-i","target":"SELF"}]},"className":"QuiltComponent_lia-component-edit-mode__lQ9Z6","showSearchIcon":false},"__typename":"QuiltComponent"},{"id":"community.widget.bannerWidget","props":{"backgroundColor":"transparent","visualEffects":{"showBottomBorder":false},"backgroundImageProps":{"backgroundSize":"COVER","backgroundPosition":"CENTER_CENTER","backgroundRepeat":"NO_REPEAT"},"fontColor":"#222222"},"__typename":"QuiltComponent"},{"id":"community.widget.breadcrumbWidget","props":{"backgroundColor":"var(--lia-bs-primary)","linkHighlightColor":"#FFFFFF","visualEffects":{"showBottomBorder":false},"backgroundOpacity":60,"linkTextColor":"#FFFFFF"},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"footer":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"var(--lia-bs-body-color)","items":[{"id":"custom.widget.Beta_Footer","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.Tag_Manager_Helper","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.Consent_Blackbar","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"__typename":"QuiltWrapper","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/ActionFeedback-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/common/ActionFeedback-1743097588266","value":{"joinedGroupHub.title":"Welcome","joinedGroupHub.message":"You are now a member of this group and are subscribed to updates.","groupHubInviteNotFound.title":"Invitation Not Found","groupHubInviteNotFound.message":"Sorry, we could not find your invitation to the group. The owner may have canceled the invite.","groupHubNotFound.title":"Group Not Found","groupHubNotFound.message":"The grouphub you tried to join does not exist. It may have been deleted.","existingGroupHubMember.title":"Already Joined","existingGroupHubMember.message":"You are already a member of this group.","accountLocked.title":"Account Locked","accountLocked.message":"Your account has been locked due to multiple failed attempts. Try again in {lockoutTime} minutes.","editedGroupHub.title":"Changes Saved","editedGroupHub.message":"Your group has been updated.","leftGroupHub.title":"Goodbye","leftGroupHub.message":"You are no longer a member of this group and will not receive future updates.","deletedGroupHub.title":"Deleted","deletedGroupHub.message":"The group has been deleted.","groupHubCreated.title":"Group Created","groupHubCreated.message":"{groupHubName} is ready to use","accountClosed.title":"Account Closed","accountClosed.message":"The account has been closed and you will now be redirected to the homepage","resetTokenExpired.title":"Reset Password Link has Expired","resetTokenExpired.message":"Try resetting your password again","invalidUrl.title":"Invalid URL","invalidUrl.message":"The URL you're using is not recognized. Verify your URL and try again.","accountClosedForUser.title":"Account Closed","accountClosedForUser.message":"{userName}'s account is closed","inviteTokenInvalid.title":"Invitation Invalid","inviteTokenInvalid.message":"Your invitation to the community has been canceled or expired.","inviteTokenError.title":"Invitation Verification Failed","inviteTokenError.message":"The url you are utilizing is not recognized. Verify your URL and try again","pageNotFound.title":"Access Denied","pageNotFound.message":"You do not have access to this area of the community or it doesn't exist","eventAttending.title":"Responded as Attending","eventAttending.message":"You'll be notified when there's new activity and reminded as the event approaches","eventInterested.title":"Responded as Interested","eventInterested.message":"You'll be notified when there's new activity and reminded as the event approaches","eventNotFound.title":"Event Not Found","eventNotFound.message":"The event you tried to respond to does not exist.","redirectToRelatedPage.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.message":"The content you are trying to access is archived","redirectToRelatedPage.message":"The content you are trying to access is archived","relatedUrl.archivalLink.flyoutMessage":"The content you are trying to access is archived View Archived Content"},"localOverride":false},"CachedAsset:component:custom.widget.Beta_MetaNav-en-1742462597562":{"__typename":"CachedAsset","id":"component:custom.widget.Beta_MetaNav-en-1742462597562","value":{"component":{"id":"custom.widget.Beta_MetaNav","template":{"id":"Beta_MetaNav","markupLanguage":"HANDLEBARS","style":null,"texts":null,"defaults":{"config":{"applicablePages":[],"description":"MetaNav menu at the top of every page.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Beta_MetaNav","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"MetaNav menu at the top of every page.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Beta_Footer-en-1742462597562":{"__typename":"CachedAsset","id":"component:custom.widget.Beta_Footer-en-1742462597562","value":{"component":{"id":"custom.widget.Beta_Footer","template":{"id":"Beta_Footer","markupLanguage":"HANDLEBARS","style":null,"texts":null,"defaults":{"config":{"applicablePages":[],"description":"DevCentral´s custom footer.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Beta_Footer","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"DevCentral´s custom footer.","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Tag_Manager_Helper-en-1742462597562":{"__typename":"CachedAsset","id":"component:custom.widget.Tag_Manager_Helper-en-1742462597562","value":{"component":{"id":"custom.widget.Tag_Manager_Helper","template":{"id":"Tag_Manager_Helper","markupLanguage":"HANDLEBARS","style":null,"texts":null,"defaults":{"config":{"applicablePages":[],"description":"Helper widget to inject Tag Manager scripts into head element","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Tag_Manager_Helper","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"Helper widget to inject Tag Manager scripts into head element","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:component:custom.widget.Consent_Blackbar-en-1742462597562":{"__typename":"CachedAsset","id":"component:custom.widget.Consent_Blackbar-en-1742462597562","value":{"component":{"id":"custom.widget.Consent_Blackbar","template":{"id":"Consent_Blackbar","markupLanguage":"HTML","style":null,"texts":null,"defaults":{"config":{"applicablePages":[],"description":"","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Consent_Blackbar","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"TEXTHTML","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":null,"form":null},"localOverride":false},"CachedAsset:text:en_US-components/community/Breadcrumb-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/community/Breadcrumb-1743097588266","value":{"navLabel":"Breadcrumbs","dropdown":"Additional parent page navigation"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBanner-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBanner-1743097588266","value":{"messageMarkedAsSpam":"This post has been marked as spam","messageMarkedAsSpam@board:TKB":"This article has been marked as spam","messageMarkedAsSpam@board:BLOG":"This post has been marked as spam","messageMarkedAsSpam@board:FORUM":"This discussion has been marked as spam","messageMarkedAsSpam@board:OCCASION":"This event has been marked as spam","messageMarkedAsSpam@board:IDEA":"This idea has been marked as spam","manageSpam":"Manage Spam","messageMarkedAsAbuse":"This post has been marked as abuse","messageMarkedAsAbuse@board:TKB":"This article has been marked as abuse","messageMarkedAsAbuse@board:BLOG":"This post has been marked as abuse","messageMarkedAsAbuse@board:FORUM":"This discussion has been marked as abuse","messageMarkedAsAbuse@board:OCCASION":"This event has been marked as abuse","messageMarkedAsAbuse@board:IDEA":"This idea has been marked as abuse","preModCommentAuthorText":"This comment will be published as soon as it is approved","preModCommentModeratorText":"This comment is awaiting moderation","messageMarkedAsOther":"This post has been rejected due to other reasons","messageMarkedAsOther@board:TKB":"This article has been rejected due to other reasons","messageMarkedAsOther@board:BLOG":"This post has been rejected due to other reasons","messageMarkedAsOther@board:FORUM":"This discussion has been rejected due to other reasons","messageMarkedAsOther@board:OCCASION":"This event has been rejected due to other reasons","messageMarkedAsOther@board:IDEA":"This idea has been rejected due to other reasons","messageArchived":"This post was archived on {date}","relatedUrl":"View Related Content","relatedContentText":"Showing related content","archivedContentLink":"View Archived Content"},"localOverride":false},"CachedAsset:text:en_US-components/tkbs/TkbArticleWidget-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/tkbs/TkbArticleWidget-1743097588266","value":{},"localOverride":false},"Category:category:Forums":{"__typename":"Category","id":"category:Forums","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Forum:board:TechnicalForum":{"__typename":"Forum","id":"board:TechnicalForum","forumPolicies":{"__typename":"ForumPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Forum:board:WaterCooler":{"__typename":"Forum","id":"board:WaterCooler","forumPolicies":{"__typename":"ForumPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:DevCentralNews":{"__typename":"Tkb","id":"board:DevCentralNews","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:GroupsCategory":{"__typename":"Category","id":"category:GroupsCategory","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:F5-Groups":{"__typename":"Category","id":"category:F5-Groups","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:CommunityGroups":{"__typename":"Category","id":"category:CommunityGroups","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Occasion:board:Events":{"__typename":"Occasion","id":"board:Events","boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"occasionPolicies":{"__typename":"OccasionPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Idea:board:Suggestions":{"__typename":"Idea","id":"board:Suggestions","boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"ideaPolicies":{"__typename":"IdeaPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:CrowdSRC":{"__typename":"Category","id":"category:CrowdSRC","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:codeshare":{"__typename":"Tkb","id":"board:codeshare","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:communityarticles":{"__typename":"Tkb","id":"board:communityarticles","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:security-insights":{"__typename":"Tkb","id":"board:security-insights","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Tkb:board:article-series":{"__typename":"Tkb","id":"board:article-series","tkbPolicies":{"__typename":"TkbPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"CachedAsset:text:en_US-components/community/Navbar-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/community/Navbar-1743097588266","value":{"community":"Community Home","inbox":"Inbox","manageContent":"Manage Content","tos":"Terms of Service","forgotPassword":"Forgot Password","themeEditor":"Theme Editor","edit":"Edit Navigation Bar","skipContent":"Skip to content","migrated-link-9":"Groups","migrated-link-7":"Technical Articles","migrated-link-8":"DevCentral News","migrated-link-1":"Technical Forum","migrated-link-10":"Community Groups","migrated-link-2":"Water Cooler","migrated-link-11":"F5 Groups","Common-external-link":"How Do I...?","migrated-link-0":"Forums","article-series":"Article Series","migrated-link-5":"Community Articles","migrated-link-6":"Articles","security-insights":"Security Insights","migrated-link-3":"CrowdSRC","migrated-link-4":"CodeShare","migrated-link-12":"Events","migrated-link-13":"Suggestions"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarHamburgerDropdown-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarHamburgerDropdown-1743097588266","value":{"hamburgerLabel":"Side Menu"},"localOverride":false},"CachedAsset:text:en_US-components/community/BrandLogo-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/community/BrandLogo-1743097588266","value":{"logoAlt":"Khoros","themeLogoAlt":"Brand Logo"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarTextLinks-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarTextLinks-1743097588266","value":{"more":"More"},"localOverride":false},"CachedAsset:text:en_US-components/authentication/AuthenticationLink-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/authentication/AuthenticationLink-1743097588266","value":{"title.login":"Sign In","title.registration":"Register","title.forgotPassword":"Forgot Password","title.multiAuthLogin":"Sign In"},"localOverride":false},"CachedAsset:text:en_US-components/nodes/NodeLink-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/nodes/NodeLink-1743097588266","value":{"place":"Place {name}"},"localOverride":false},"QueryVariables:TopicReplyList:message:286251:1":{"__typename":"QueryVariables","id":"TopicReplyList:message:286251:1","value":{"id":"message:286251","first":10,"sorts":{"postTime":{"direction":"ASC"}},"repliesFirst":3,"repliesFirstDepthThree":1,"repliesSorts":{"postTime":{"direction":"ASC"}},"useAvatar":true,"useAuthorLogin":true,"useAuthorRank":true,"useBody":true,"useKudosCount":true,"useTimeToRead":false,"useMedia":false,"useReadOnlyIcon":false,"useRepliesCount":true,"useSearchSnippet":false,"useAcceptedSolutionButton":false,"useSolvedBadge":false,"useAttachments":false,"attachmentsFirst":5,"useTags":true,"useNodeAncestors":false,"useUserHoverCard":false,"useNodeHoverCard":false,"useModerationStatus":true,"usePreviewSubjectModal":false,"useMessageStatus":true}},"ROOT_MUTATION":{"__typename":"Mutation"},"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/QueryHandler-1743097588266","value":{"title":"Query Handler"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarDropdownToggle-1743097588266","value":{"ariaLabelClosed":"Press the down arrow to open the menu"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageView/MessageViewStandard-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageView/MessageViewStandard-1743097588266","value":{"anonymous":"Anonymous","author":"{messageAuthorLogin}","authorBy":"{messageAuthorLogin}","board":"{messageBoardTitle}","replyToUser":" to {parentAuthor}","showMoreReplies":"Show More","replyText":"Reply","repliesText":"Replies","markedAsSolved":"Marked as Solved","movedMessagePlaceholder.BLOG":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.TKB":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.FORUM":"{count, plural, =0 {This reply has been} other {These replies have been} }","movedMessagePlaceholder.IDEA":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.OCCASION":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholderUrlText":"moved.","messageStatus":"Status: ","statusChanged":"Status changed: {previousStatus} to {currentStatus}","statusAdded":"Status added: {status}","statusRemoved":"Status removed: {status}","labelExpand":"expand replies","labelCollapse":"collapse replies","unhelpfulReason.reason1":"Content is outdated","unhelpfulReason.reason2":"Article is missing information","unhelpfulReason.reason3":"Content is for a different Product","unhelpfulReason.reason4":"Doesn't match what I was searching for"},"localOverride":false},"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/ThreadedReplyList-1743097588266","value":{"title":"{count, plural, one{# Reply} other{# Replies}}","title@board:BLOG":"{count, plural, one{# Comment} other{# Comments}}","title@board:TKB":"{count, plural, one{# Comment} other{# Comments}}","title@board:IDEA":"{count, plural, one{# Comment} other{# Comments}}","title@board:OCCASION":"{count, plural, one{# Comment} other{# Comments}}","noRepliesTitle":"No Replies","noRepliesTitle@board:BLOG":"No Comments","noRepliesTitle@board:TKB":"No Comments","noRepliesTitle@board:IDEA":"No Comments","noRepliesTitle@board:OCCASION":"No Comments","noRepliesDescription":"Be the first to reply","noRepliesDescription@board:BLOG":"Be the first to comment","noRepliesDescription@board:TKB":"Be the first to comment","noRepliesDescription@board:IDEA":"Be the first to comment","noRepliesDescription@board:OCCASION":"Be the first to comment","messageReadOnlyAlert:BLOG":"Comments have been turned off for this post","messageReadOnlyAlert:TKB":"Comments have been turned off for this article","messageReadOnlyAlert:IDEA":"Comments have been turned off for this idea","messageReadOnlyAlert:FORUM":"Replies have been turned off for this discussion","messageReadOnlyAlert:OCCASION":"Comments have been turned off for this event"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyCallToAction-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyCallToAction-1743097588266","value":{"leaveReply":"Leave a reply...","leaveReply@board:BLOG@message:root":"Leave a comment...","leaveReply@board:TKB@message:root":"Leave a comment...","leaveReply@board:IDEA@message:root":"Leave a comment...","leaveReply@board:OCCASION@message:root":"Leave a comment...","repliesTurnedOff.FORUM":"Replies are turned off for this topic","repliesTurnedOff.BLOG":"Comments are turned off for this topic","repliesTurnedOff.TKB":"Comments are turned off for this topic","repliesTurnedOff.IDEA":"Comments are turned off for this topic","repliesTurnedOff.OCCASION":"Comments are turned off for this topic","infoText":"Stop poking me!"},"localOverride":false},"Rank:rank:47":{"__typename":"Rank","id":"rank:47","position":3,"name":"Historic F5 Account","color":"949494","icon":null,"rankStyle":"OUTLINE"},"User:user:106863":{"__typename":"User","id":"user:106863","uid":106863,"login":"David_Holmes_12","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2012-12-19T00:00:00.000-08:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/m_assets/avatars/default/avatar-4.svg?time=0"},"rank":{"__ref":"Rank:rank:47"},"entityType":"USER","eventPath":"community:zihoc95639/user:106863"},"ModerationData:moderation_data:286252":{"__typename":"ModerationData","id":"moderation_data:286252","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"TkbReplyMessage:message:286252":{"__typename":"TkbReplyMessage","author":{"__ref":"User:user:106863"},"id":"message:286252","revisionNum":1,"uid":286252,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Tkb:board:TechnicalArticles"},"parent":{"__ref":"TkbTopicMessage:message:286251"},"conversation":{"__ref":"Conversation:conversation:286251"},"subject":"Re: TLS Fingerprinting - a method for identifying a TLS client without decrypting","moderationData":{"__ref":"ModerationData:moderation_data:286252"},"body":"
That's some hardcore stuff, Kevin. Awesome.
","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"53","kudosSumWeight":0,"repliesCount":0,"postTime":"2017-01-01T14:09:05.000-08:00","lastPublishTime":"2017-01-01T14:09:05.000-08:00","metrics":{"__typename":"MessageMetrics","views":4844},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"TKB_REPLY","eventPath":"category:Articles/community:zihoc95639board:TechnicalArticles/message:286251/message:286252","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"User:user:48797":{"__typename":"User","id":"user:48797","uid":48797,"login":"Jason_Adams","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2013-02-28T00:00:00.000-08:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/images/dS00ODc5Ny0yMTkyM2lGNjVFOTBFQzlFNTIzNERD"},"rank":{"__ref":"Rank:rank:28"},"entityType":"USER","eventPath":"community:zihoc95639/user:48797"},"ModerationData:moderation_data:286253":{"__typename":"ModerationData","id":"moderation_data:286253","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"TkbReplyMessage:message:286253":{"__typename":"TkbReplyMessage","author":{"__ref":"User:user:48797"},"id":"message:286253","revisionNum":2,"uid":286253,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Tkb:board:TechnicalArticles"},"parent":{"__ref":"TkbTopicMessage:message:286251"},"conversation":{"__ref":"Conversation:conversation:286251"},"subject":"Re: TLS Fingerprinting - a method for identifying a TLS client without decrypting","moderationData":{"__ref":"ModerationData:moderation_data:286253"},"body":"
Thank you for writing this article. I have a couple of critiques, and a couple fingerprints to add to your list as well.
\n
The caller iRule should have 'clientip' and 'serverip' arguments sent to Library-Rule.
\n
With the assumption you wanted to log 'serverip' as the Virtual Server address, this is what the line should look like:
\n
set fingerprint [call Library-Rule::fingerprintTLS [TCP::payload] ${rlen} ${outer_sslver} ${inner_sslver} [IP::client_addr] [IP::local_addr]]\n
\n\n
Also, the 4th 'Additional Ciphers' string contains a double-quote in the middle of the string, causing copy-paste of your additional ciphers to fail with a syntax error.
And here are two more to add to your 'Additional Ciphers' list:
\n
\"0303+0303+0038+C02CC02BC030C02F009F009EC024C023C028C027C00AC009C014C01300390033009D009C003D003C0035002F000A006A0040003800320013+1+00+0005000A000B000D0023001000175500FF01+001D00170018+040105010201040305030203020206010603+00\" := \"Internet Explorer 11.447.14393.0(Win 10)\",\n\"0301+0303+0022+C02BC02FC02CC030CCA9CCA8CC14CC13C009C013C00AC014009C009D002F0035000A+1+00+FF0100170023000D0005001200107550000B000A+001D00170018+06010603050105030401040302010203+00\" := \"Chrome 55.0.2883.87\",\n
\n\n
This is a great exercise. I don't doubt this will be useful in the future.
","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"203","kudosSumWeight":0,"repliesCount":0,"postTime":"2017-01-03T09:51:57.000-08:00","lastPublishTime":"2023-06-03T23:29:54.194-07:00","metrics":{"__typename":"MessageMetrics","views":4813},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"TKB_REPLY","eventPath":"category:Articles/community:zihoc95639board:TechnicalArticles/message:286251/message:286253","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"ModerationData:moderation_data:286254":{"__typename":"ModerationData","id":"moderation_data:286254","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"TkbReplyMessage:message:286254":{"__typename":"TkbReplyMessage","author":{"__ref":"User:user:130391"},"id":"message:286254","revisionNum":1,"uid":286254,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Tkb:board:TechnicalArticles"},"parent":{"__ref":"TkbTopicMessage:message:286251"},"conversation":{"__ref":"Conversation:conversation:286251"},"subject":"Re: TLS Fingerprinting - a method for identifying a TLS client without decrypting","moderationData":{"__ref":"ModerationData:moderation_data:286254"},"body":"
Thanks Jason! I've updated the article.
","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"49","kudosSumWeight":0,"repliesCount":0,"postTime":"2017-01-03T23:31:34.000-08:00","lastPublishTime":"2017-01-03T23:31:34.000-08:00","metrics":{"__typename":"MessageMetrics","views":4823},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"TKB_REPLY","eventPath":"category:Articles/community:zihoc95639board:TechnicalArticles/message:286251/message:286254","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/cmstNDEtSzFzVEth\"}":{"__typename":"AssociatedImage","url":"https://community.f5.com/t5/s/zihoc95639/images/cmstNDEtSzFzVEth","height":0,"width":0,"mimeType":"image/svg+xml"},"Rank:rank:41":{"__typename":"Rank","id":"rank:41","position":18,"name":"Nimbostratus","color":"CCCCCC","icon":{"__ref":"AssociatedImage:{\"url\":\"https://community.f5.com/t5/s/zihoc95639/images/cmstNDEtSzFzVEth\"}"},"rankStyle":"FILLED"},"User:user:266364":{"__typename":"User","id":"user:266364","uid":266364,"login":"Lee_Brotherston","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2019-05-05T04:29:51.000-07:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/m_assets/avatars/default/avatar-12.svg?time=0"},"rank":{"__ref":"Rank:rank:41"},"entityType":"USER","eventPath":"community:zihoc95639/user:266364"},"ModerationData:moderation_data:286255":{"__typename":"ModerationData","id":"moderation_data:286255","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"TkbReplyMessage:message:286255":{"__typename":"TkbReplyMessage","author":{"__ref":"User:user:266364"},"id":"message:286255","revisionNum":1,"uid":286255,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Tkb:board:TechnicalArticles"},"parent":{"__ref":"TkbTopicMessage:message:286251"},"conversation":{"__ref":"Conversation:conversation:286251"},"subject":"Re: TLS Fingerprinting - a method for identifying a TLS client without decrypting","moderationData":{"__ref":"ModerationData:moderation_data:286255"},"body":"
If anybody would like to see missing fingerprints added to the main repo, I'm more than happy to do so. Pull requests are more than welcome, or if you prefer feel free to email me: lee+f5@squarelemon.com
","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"203","kudosSumWeight":0,"repliesCount":0,"postTime":"2017-01-05T03:42:43.000-08:00","lastPublishTime":"2017-01-05T03:42:43.000-08:00","metrics":{"__typename":"MessageMetrics","views":4818},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"TKB_REPLY","eventPath":"category:Articles/community:zihoc95639board:TechnicalArticles/message:286251/message:286255","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"User:user:180433":{"__typename":"User","id":"user:180433","uid":180433,"login":"nomæd","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2019-05-16T03:11:31.000-07:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/m_assets/avatars/default/avatar-9.svg?time=0"},"rank":{"__ref":"Rank:rank:28"},"entityType":"USER","eventPath":"community:zihoc95639/user:180433"},"ModerationData:moderation_data:286256":{"__typename":"ModerationData","id":"moderation_data:286256","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"TkbReplyMessage:message:286256":{"__typename":"TkbReplyMessage","author":{"__ref":"User:user:180433"},"id":"message:286256","revisionNum":2,"uid":286256,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Tkb:board:TechnicalArticles"},"parent":{"__ref":"TkbTopicMessage:message:286251"},"conversation":{"__ref":"Conversation:conversation:286251"},"subject":"Re: TLS Fingerprinting - a method for identifying a TLS client without decrypting","moderationData":{"__ref":"ModerationData:moderation_data:286256"},"body":"
While
regsub {(.)A\\1A} ${exte} \"\" exte
is fine in CLI, BIG-IP's TCL complains about this:\n
warning: [\"\\1\" has no meaning. Did you mean \"\\\\1\" or \"1\"?][{(.)A\\1A}]
","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"172","kudosSumWeight":0,"repliesCount":0,"postTime":"2018-11-01T03:31:10.000-07:00","lastPublishTime":"2023-06-05T11:40:08.196-07:00","metrics":{"__typename":"MessageMetrics","views":4818},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"TKB_REPLY","eventPath":"category:Articles/community:zihoc95639board:TechnicalArticles/message:286251/message:286256","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"User:user:405843":{"__typename":"User","id":"user:405843","uid":405843,"login":"Saravanan_M_K","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2011-07-12T01:00:00.000-07:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/images/dS00MDU4NDMtMjE5MTNpMDM3NEEzRkVCRjUxQTBGNQ"},"rank":{"__ref":"Rank:rank:28"},"entityType":"USER","eventPath":"community:zihoc95639/user:405843"},"ModerationData:moderation_data:286257":{"__typename":"ModerationData","id":"moderation_data:286257","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"TkbReplyMessage:message:286257":{"__typename":"TkbReplyMessage","author":{"__ref":"User:user:405843"},"id":"message:286257","revisionNum":3,"uid":286257,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Tkb:board:TechnicalArticles"},"parent":{"__ref":"TkbTopicMessage:message:286251"},"conversation":{"__ref":"Conversation:conversation:286251"},"subject":"Re: TLS Fingerprinting - a method for identifying a TLS client without decrypting","moderationData":{"__ref":"ModerationData:moderation_data:286257"},"body":"
<quote>\nWhile testing this I noticed that newer versions of Chrome and Opera added what looked like \"markers\" to the ciphersuite list, extensions list, and elliptic curves list (ex. 9A9A, 5A5A, EAEA, BABA - always some alphanumeric value, followed by 'A', and repeated.). A cursory search didn't explain what these are, so maybe someone will know and report back.\n</quote>
I know this is old article and by now you might have already figured it out. I am replying it for the readers just in case if they are not aware of this. the strange codes (e.g. 0x9a9a, etc) that you see are TLS GREASE values. For more info see the internet draft https://www.ietf.org/id/draft-ietf-tls-grease-04.txt (as of today it is in version 4).
","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"209","kudosSumWeight":0,"repliesCount":0,"postTime":"2019-10-02T02:48:04.000-07:00","lastPublishTime":"2023-06-01T14:42:43.003-07:00","metrics":{"__typename":"MessageMetrics","views":4818},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"TKB_REPLY","eventPath":"category:Articles/community:zihoc95639board:TechnicalArticles/message:286251/message:286257","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"User:user:26559":{"__typename":"User","id":"user:26559","uid":26559,"login":"ColinConrad","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2019-06-07T07:52:52.000-07:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://community.f5.com/t5/s/zihoc95639/m_assets/avatars/default/avatar-3.svg?time=0"},"rank":{"__ref":"Rank:rank:41"},"entityType":"USER","eventPath":"community:zihoc95639/user:26559"},"ModerationData:moderation_data:286258":{"__typename":"ModerationData","id":"moderation_data:286258","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"TkbReplyMessage:message:286258":{"__typename":"TkbReplyMessage","author":{"__ref":"User:user:26559"},"id":"message:286258","revisionNum":1,"uid":286258,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Tkb:board:TechnicalArticles"},"parent":{"__ref":"TkbTopicMessage:message:286251"},"conversation":{"__ref":"Conversation:conversation:286251"},"subject":"Re: TLS Fingerprinting - a method for identifying a TLS client without decrypting","moderationData":{"__ref":"ModerationData:moderation_data:286258"},"body":"
Can we get an updated version of the fingerprintTLS procedure that corrects the four TCL warnings reported in BIG-IP versions 14 and 15 when running a \"load sys config verify\"? One was mentioned in a comment above. Two are trivial to resolve, but the other two aren't as obvious.
","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"203","kudosSumWeight":0,"repliesCount":0,"postTime":"2020-09-09T12:47:11.000-07:00","lastPublishTime":"2020-09-09T12:47:11.000-07:00","metrics":{"__typename":"MessageMetrics","views":4813},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"TKB_REPLY","eventPath":"category:Articles/community:zihoc95639board:TechnicalArticles/message:286251/message:286258","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"ModerationData:moderation_data:286259":{"__typename":"ModerationData","id":"moderation_data:286259","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"TkbReplyMessage:message:286259":{"__typename":"TkbReplyMessage","author":{"__ref":"User:user:48797"},"id":"message:286259","revisionNum":2,"uid":286259,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Tkb:board:TechnicalArticles"},"parent":{"__ref":"TkbTopicMessage:message:286251"},"conversation":{"__ref":"Conversation:conversation:286251"},"subject":"Re: TLS Fingerprinting - a method for identifying a TLS client without decrypting","moderationData":{"__ref":"ModerationData:moderation_data:286259"},"body":"
<quote>\nIt's also be a little slow, again due to my aggregious lack of sed/awk/grep (and regex) foo, but it should still finish in less than a minute.\n</quote>
I've created a Python parser here that is fairly zippy.
","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"215","kudosSumWeight":0,"repliesCount":0,"postTime":"2021-03-22T17:14:59.000-07:00","lastPublishTime":"2023-06-04T21:00:31.311-07:00","metrics":{"__typename":"MessageMetrics","views":4818},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"TKB_REPLY","eventPath":"category:Articles/community:zihoc95639board:TechnicalArticles/message:286251/message:286259","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"CachedAsset:text:en_US-components/messages/MessageSubject-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageSubject-1743097588266","value":{"noSubject":"(no subject)"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBody-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBody-1743097588266","value":{"showMessageBody":"Show More","mentionsErrorTitle":"{mentionsType, select, board {Board} user {User} message {Message} other {}} No Longer Available","mentionsErrorMessage":"The {mentionsType} you are trying to view has been removed from the community.","videoProcessing":"Video is being processed. Please try again in a few minutes.","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCustomFields-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCustomFields-1743097588266","value":{"CustomField.default.label":"Value of {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageRevision-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageRevision-1743097588266","value":{"lastUpdatedDatePublished":"{publishCount, plural, one{Published} other{Updated}} {date}","lastUpdatedDateDraft":"Created {date}","version":"Version {major}.{minor}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyButton-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyButton-1743097588266","value":{"repliesCount":"{count}","title":"Reply","title@board:BLOG@message:root":"Comment","title@board:TKB@message:root":"Comment","title@board:IDEA@message:root":"Comment","title@board:OCCASION@message:root":"Comment"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageAuthorBio-1743097588266","value":{"sendMessage":"Send Message","actionMessage":"Follow this blog board to get notified when there's new activity","coAuthor":"CO-PUBLISHER","contributor":"CONTRIBUTOR","userProfile":"View Profile","iconlink":"Go to {name} {type}"},"localOverride":false},"CachedAsset:text:en_US-components/guides/GuideBottomNavigation-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/guides/GuideBottomNavigation-1743097588266","value":{"nav.label":"Previous/Next Page","nav.previous":"Previous","nav.next":"Next"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserLink-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserLink-1743097588266","value":{"authorName":"View Profile: {author}","anonymous":"Anonymous"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserRank-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserRank-1743097588266","value":{"rankName":"{rankName}","userRank":"Author rank {rankName}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTime-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTime-1743097588266","value":{"postTime":"Published: {time}","lastPublishTime":"Last Update: {time}","conversation.lastPostingActivityTime":"Last posting activity time: {time}","conversation.lastPostTime":"Last post time: {time}","moderationData.rejectTime":"Rejected time: {time}"},"localOverride":false},"CachedAsset:text:en_US-components/customComponent/CustomComponent-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/customComponent/CustomComponent-1743097588266","value":{"errorMessage":"Error rendering component id: {customComponentId}","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserAvatar-1743097588266","value":{"altText":"{login}'s avatar","altTextGeneric":"User's avatar"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/ranks/UserRankLabel-1743097588266","value":{"altTitle":"Icon for {rankName} rank"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserRegistrationDate-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserRegistrationDate-1743097588266","value":{"noPrefix":"{date}","withPrefix":"Joined {date}"},"localOverride":false},"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1743097588266":{"__typename":"CachedAsset","id":"text:en_US-components/tags/TagView/TagViewChip-1743097588266","value":{"tagLabelName":"Tag name {tagName}"},"localOverride":false}}}},"page":"/kbs/TkbMessagePage/TkbMessagePage","query":{"boardId":"technicalarticles","messageSubject":"tls-fingerprinting---a-method-for-identifying-a-tls-client-without-decrypting","messageId":"286251"},"buildId":"q_bLpq2mflH0BeZigxpj6","runtimeConfig":{"buildInformationVisible":false,"logLevelApp":"info","logLevelMetrics":"info","openTelemetryClientEnabled":false,"openTelemetryConfigName":"f5","openTelemetryServiceVersion":"25.2.0","openTelemetryUniverse":"prod","openTelemetryCollector":"http://localhost:4318","openTelemetryRouteChangeAllowedTime":"5000","apolloDevToolsEnabled":false,"inboxMuteWipFeatureEnabled":false},"isFallback":false,"isExperimentalCompile":false,"dynamicIds":["./components/customComponent/CustomComponent/CustomComponent.tsx","./components/community/Navbar/NavbarWidget.tsx","./components/community/Breadcrumb/BreadcrumbWidget.tsx","./components/tkbs/TkbArticleWidget/TkbArticleWidget.tsx","./components/messages/MessageView/MessageViewStandard/MessageViewStandard.tsx","./components/messages/ThreadedReplyList/ThreadedReplyList.tsx","./components/customComponent/CustomComponentContent/TemplateContent.tsx","../shared/client/components/common/List/UnstyledList/UnstyledList.tsx","./components/messages/MessageView/MessageView.tsx","./components/customComponent/CustomComponentContent/HtmlContent.tsx","./components/customComponent/CustomComponentContent/CustomComponentScripts.tsx","../shared/client/components/common/List/UnwrappedList/UnwrappedList.tsx","./components/tags/TagView/TagView.tsx","./components/tags/TagView/TagViewChip/TagViewChip.tsx"],"appGip":true,"scriptLoader":[]}