Forum Discussion
F5 iRule Example integrating HMAC
I currently utilize a static key value to create a custom encrypted cookie to validate user session authenticity via cookie values. I would like to move over to using an HMAC solution, but having issues with the currently provided example. I am currently running 10.2 code on my LTM, so some of the current information on 11.x using new commands is irrelevant. Does anyone have an example laying around or can translate the example at https://devcentral.f5.com/codeshare/hmac for me? Thanks
- gpoverlandNimbostratus
Kai,, I might have run into an issue.. It appears PROC is disabled in 10.2 code. Do you know of an alternative?
Haha... thats is a good one. You've asking for pre-11.x support and I recommend stuff for +11.4... -.-
Well you could simply copy the contained [proc] code into your main iRule (as often you need) and simply set $message $prekey and $hmac in front of and rewrite the [return] part to become an addition variable.
If this clutters your iRule code to much, then use TCL marcos by storing the code into $static::variables and [eval] them as needed. See below...
when RULE_INIT { set static::crypto_sign { set bsize 64 if { [string length $prekey] > $bsize } { set key [sha256 $prekey] } else { set key $prekey } set ipad "" set opad "" for { set j 0 }{ $j < [string length $key] }{ incr j }{ binary scan $key @${j}H2 k set o [expr 0x$k ^ 0x5c] set i [expr 0x$k ^ 0x36] append ipad [format %c $i] append opad [format %c $o] } for { }{ $j < $bsize }{ incr j }{ append ipad 6 append opad \\ } set token [sha256 $opad[sha256 "${ipad}${message}"]] binary scan $token H* hmac_output } set static::crypto_verify { set bsize 64 if { [string length $prekey] > $bsize } { set key [sha256 $prekey] } else { set key $prekey } set ipad "" set opad "" for { set j 0 }{ $j < [string length $key] }{ incr j }{ binary scan $key @${j}H2 k set o [expr 0x$k ^ 0x5c] set i [expr 0x$k ^ 0x36] append ipad [format %c $i] append opad [format %c $o] } for { }{ $j < $bsize }{ incr j }{ append ipad 6 append opad \\ } set token [sha256 $opad[sha256 "${ipad}${message}"]] binary scan $token H* hmac_output if { $hmac_output eq $hmac_input } then { set hmac_valid 1 } else { set hmac_valid 0 } } } when HTTP_REQUEST { Sign set message "hallo world" set prekey "1234" eval $static::crypto_sign log -noname local0. "HMAC Code is = $hmac_output" Validate set message "hallo world" set prekey "1234" set hmac_input $hmac_output eval $static::crypto_verify log -noname local0. "HMAC Code verified = $hmac_valid" }
Note: I've changed a little the $variable name to avoid conflicts. But didn't tested (nor saved) the code at all. Hope this works still out for you. If not, then complain and I will take another look for you on monday...
Cheers, Kai
- gpoverlandNimbostratus
Kai,
I ran into an issue when I rolled your code into action. Apparently, as I validate the proceeding connections, the $hmac_output variable loses its value, and the connection terminates. Any ideas? I plan to reject any session that hmac_output does not match hmac_input, but was waiting to get the rest of the iRULE working now.
Hi Dtwesten,
I was able to sniff through the HTML source of your post...
General notes
The condition [HTTP::uri] starts_with "/" is always true. So you can savely remove every instance of this condition.
You parse the request for [HTTP::cookie exists "hmac_01"] but you set [HTTP::cookie insert "hmac_74"]
You should unset $hmac_output right after [HTTP::cookie insert name "hmac_74" value $hmac_output path "/"]
You should add HttpOnly and Secure flags to the HMAC cookie if security is a concern.
Regarding your error
The line...
set hmac_input $hmac_output
... should be changed to...
set hmac_input [HTTP::cookie value "hmac_74"]
The rest looks good so far...
Cheers, Kai
- gpoverlandNimbostratus
I figured it out. I needed to use the encrypted cookie that housed the HMAC message from the original request instead of the variable set. Thanks for taking a look.
Hi Dtwesten,
I really appreciate your idea to sign session cookies based on certificate information. It allow us to bind a usual session cookie (e.g. requiring AuthXYZ) to a specific certificate without touching the app at all. I'm somewhat sure that I'll use this method in the near future... 😉
You may take a deeply look below. Its a quick coding in notepad (did not tested the iRule) to outline how I would implement such a functionality. It borows your idea to HMAC sign a cookie based on available certificate information, but also fixes the HMAC cookie problematic which I've mentioned in my previous comment...
V10.x
when CLIENTSSL_HANDSHAKE { if { [SSL::cert count] > 0 } then { if { [set cert_value [X509::subject [SSL::cert 0]]] eq "" } then { reject } } else { reject } } when HTTP_REQUEST { if { ( [set app_cookie [HTTP::cookie value "app"]] ne "" ) and ( [set hmac_input [HTTP::cookie value "hmac_01"]] ne "" ) } then { set message "$app_cookie:$cert_value" set prekey "12345678" eval $static::crypto_verify if { not $hmac_valid } then { HTTP::cookie remove "app" } } else { HTTP::cookie remove "app" } HTTP::cookie remove "hmac_01" } when HTTP_RESPONSE { if { [set app_cookie [HTTP::cookie value "app"]] ne "" } then { set message "$app_cookie:$cert_value" set prekey "12345678" eval $static::crypto_sign HTTP::header insert "Set-Cookie" "hmac_01=$hmac_output; HttpOnly; Secure; Path=/" } }
V11.x+
when CLIENTSSL_HANDSHAKE { if { [SSL::cert count] > 0 } then { if { [set cert_value [X509::subject [SSL::cert 0]]] eq "" } then { reject } } else { reject } } when HTTP_REQUEST { if { ( [set app_cookie [HTTP::cookie value "app"]] ne "" ) and ( [set hmac_input [HTTP::cookie value "hmac_01"]] ne "" ) } then { if { not [CRYPTO::verify -alg hmac-sha256 -key "12345678" -signature $hmac_input "$app_cookie:$cert_value"] } then { HTTP::cookie remove "app" } } else { HTTP::cookie remove "app" } HTTP::cookie remove "hmac_01" } when HTTP_RESPONSE { if { [set app_cookie [HTTP::cookie value "app"]] ne "" } then { binary scan [CRYPTO::sign -alg hmac-sha256 -key "12345678" "$app_cookie:$cert_value"] H* hmac_output HTTP::header insert "Set-Cookie" "hmac_01=$hmac_output; HttpOnly; Secure; Path=/" } }
Note: The next step would be to add some short living [table] or TMM caches to offload the per-request verification and (hopefully not) a per-response cookie generation...
Thanks and Cheers, Kai
- gpoverlandNimbostratus
Nice.. I like what you did there. Instead of rejecting the session, you just deleted the app cookie forcing a new login.. like I do! interesting you mention the TMM caches.. I was just reading into it and other options. I am concerned that this per HTTP request verification will negatively impact overall performance. I'm open to suggestions..
As I've said. I've just borrowed your brilliant idea... 😉
There are plenty of articles on how to use the [table] command to store and query arbitrary data (aka. build CMP-aware caches). But I don't believe you will find any information on the internet how to create TMM caches (aka. CMP-unaware) to avoid cross-TMM communication. I guess the only information you'll find on this topic is a little warning to not use this technique...
"Important: While it is possible to use the set command to modify a static global variable within the iRule and outside of the RULE_INIT event, such modifications will not be propagated to each TMM instance; they will be visible to only the TMM process on which the modification was made, resulting in inconsistent values for the static global variable across TMM instances. As a result, F5 strongly recommends that you do not update the value of any static global variable within the iRule." -SOL13033
... but still doing so would be very helpful for your HMAC verification to shrink the required CPU cycles to a great extend.
Let me see if I find some time in the evening to code you such an CMP-unaware cache. Its not that difficult to implement, so stay tuned...
Cheers, Kai
Hi Dtwesten,
I found some time to code the mentioned TMM cache. I've already compared the performance of my previously posted v10.x iRule, with the new version containing the TMM cache with added garbage collection and some other little performance optimization. Here are the results...
Old iRule: (without TMM caching)
----------------------------------------------- Ltm::Rule Event: iRule_HMAC_VERIFY:HTTP_REQUEST ----------------------------------------------- Priority 500 Executions Total 1.0K Failures 0 Aborts 0 CPU Cycles on Executing Average 456.2K Maximum 584.0K Minimum 325.8K
Optimized iRule: (with TMM caching)
----------------------------------------------- Ltm::Rule Event: iRule_HMAC_VERIFY:HTTP_REQUEST ----------------------------------------------- Priority 500 Executions Total 1.0K Failures 0 Aborts 0 CPU Cycles on Executing Average 80.0K Maximum 532.3K Minimum 41.2K Note: The result contains the TMM cache creation and initial fresh HMAC calculation.
The TMM cache is doing a pretty good job. The cache is able to increase the per-request HMAC verification throughput for ~570% or in other words it will reduce the required Avg. CPU cycles down to ~17,5%.
Let me know, if you're able to optimize the code even further, or if you find some unhandled exceptions. But I believe the new iRule is already somewhat save to use within production environments... 😉
Optimized v10.x Code
when RULE_INIT { set static::tmm_cache_timestamp "" set static::crypto_sign_key "12345678" set static::genarate_hmac_token { set bsize 64 if { [string length $static::crypto_sign_key] > $bsize } { set key [sha256 $static::crypto_sign_key] } else { set key $static::crypto_sign_key } set ipad "" set opad "" for { set j 0 }{ $j < [string length $key] }{ incr j }{ binary scan $key @${j}H2 k set o [expr 0x$k ^ 0x5c] set i [expr 0x$k ^ 0x36] append ipad [format %c $i] append opad [format %c $o] } for { }{ $j < $bsize }{ incr j }{ append ipad 6 append opad \\ } set token [sha256 $opad[sha256 "${ipad}$app_cookie:$cert_value"]] binary scan $token H* hmac_output } } when CLIENTSSL_HANDSHAKE { if { [SSL::cert count] > 0 } then { if { [set cert_value [X509::subject [SSL::cert 0]]] eq "" } then { reject } } else { reject } } when HTTP_REQUEST { if { ( [set app_cookie [HTTP::cookie value "ASP.NET_SessionId"]] ne "" ) and ( [set hmac_input [HTTP::cookie value "hmac_01"]] ne "" ) } then { if { [catch { if { [expr { [clock seconds] & 0xFFFFFF00 } ] eq $static::tmm_cache_timestamp } then { if { $static::hmac_values($app_cookie:$cert_value) eq $hmac_input } then { log -noname local0.debug "Request : Cached HMAC Verified" } else { log -noname local0.debug "Request : Cached HMAC not Verified" HTTP::cookie remove "ASP.NET_SessionId" } } else { log -noname local0.debug "Request : TMM Cache Bucket Recycling" set static::tmm_cache_timestamp [expr { [clock seconds] & 0xFFFFFF00 } ] unset -nocomplain static::hamc_values Jumping to the [catch] exception return } }]} then { log local0.debug "Request : Generate fresh HMAC" eval $static::genarate_hmac_token if { $hmac_output eq $hmac_input } then { log -noname local0.debug "Request : Fresh HMAC Verified" set static::hmac_values($app_cookie:$cert_value) $hmac_output } else { log -noname local0.debug "Request : Fresh HMAC not Verified" HTTP::cookie remove "ASP.NET_SessionId" } } } else { HTTP::cookie remove "ASP.NET_SessionId" } HTTP::cookie remove "hmac_01" } when HTTP_RESPONSE { if { [set app_cookie [HTTP::cookie value "ASP.NET_SessionId"]] ne "" } then { if { [catch { HTTP::header insert "Set-Cookie" "hmac_01=$static::hmac_values($app_cookie:$cert_value); HttpOnly; Secure; Path=/" log -noname local0.debug "Response : Cached HMAC cookie injected" }]} then { eval $static::genarate_hmac_token set static::hmac_values($app_cookie:$cert_value) $hmac_output HTTP::header insert "Set-Cookie" "hmac_01=$hmac_output; HttpOnly; Secure; Path=/" log -noname local0.debug "Response : Fresh HMAC cookie injected" } } }
Note: The bitmask of 0xFFFFFF00 (=256 seconds) defines the TMM cache garbage collection interval. When a garbage collection occurs, then the entire HMAC cache is getting flushed and recreated on demand on each TMM core independently.
Note: The HTTP_RESPONSE event is optimized for sites which are reapplying the cookie on every response. The code could be further optimized for those sites which are setting the cookie just once. The required change would be an [info exist] instead of [catch] syntax. But the difference would be marginal and only for this single response...
Cheers, Kai
- gpoverlandNimbostratus
WOW! nicely done.. will get back to playing on this next week, but for now.. Great work.
- You're welcome! FYI: In the meantime I was able to cutoff additional 20% overhead for the per-request verification. It takes now roughly 65k CPU clicks to verify a single HMAC protected HTTP request (while holding an HMAC cache bucket of ~1million unique HMAC tokens). I'm currently still collecting some real-world experiences with this code, but I'm trying to publish an updated version at the end of the week to the CodeShare. Are you fine when I include some credits for you as the origin of this lovely idea? ;-) Cheers, Kai
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com