F5 BIG-IP Access Policy Manager (APM) - Google Authenticator and Microsoft Authenticator
Introduction
In our walkthrough we are refreshing an existing time-based one-time password (TOTP) deployment Two-Factor Authentication With Google Authenticator And APM
In our walkthrough we are following the below assumptions,
- Secret key is generated outside of F5 and saved to Active Directory (AD) user attribute.
- F5 APM should be able to query AD user attribute (for example, in our case it's called serialNumber).
- We have two separate portals,
- One portal for Token generation and QR scanning.
- One portal for Application access.
Lab guide
Phase 1: Token Generation
- User logs in to the token generation portal and authenticates with AD credentials.
- F5 APM authenticates the user with AD and query the attribute for the secret key.
- F5 APM presnets the QR code for the user to be scanned whether by Google or Microsoft Authenticators.
Phase 2: Token verification
- User Access the application and authenticates with AD credentials.
- Once user is successfully authenticated, the user is prompted to provide the one-time password (OTP).
- Once F5 verifies the provided OTP, the user is allowed to access the application.
Verification iRule
when ACCESS_POLICY_AGENT_EVENT {
if { [ACCESS::policy agent_id] eq "ga_code_verify" } {
### Google Authenticator verification settings ###
# lock the user out after x attempts for a period of x seconds
set static::lockout_attempts 3
set static::lockout_period 30
# logon page session variable name for code attempt form field
set static::ga_code_form_field "ga_code_attempt"
# key (shared secret) storage method: ldap, ad, or datagroup
set static::ga_key_storage "ad"
# LDAP attribute for key if storing in LDAP (optional)
set static::ga_key_ldap_attr "google_auth_key"
# Active Directory attribute for key if storing in AD (optional)
set static::ga_key_ad_attr [ACCESS::session data get "session.ad.last.attr.serialNumber"]
# datagroup name if storing key in a datagroup (optional)
set static::ga_key_dg "google_auth_keys"
#####################################
### DO NOT MODIFY BELOW THIS LINE ###
#####################################
# set lockout table
set static::lockout_state_table "[virtual name]_lockout_status"
# set variables from APM logon page
set username [ACCESS::session data get session.logon.last.username]
set ga_code_attempt [ACCESS::session data get session.logon.last.$static::ga_code_form_field]
# retrieve key from specified storage
set ga_key ""
switch $static::ga_key_storage {
ldap {
set ga_key [ACCESS::session data get session.ldap.last.attr.$static::ga_key_ldap_attr]
}
ad {
set ga_key [ACCESS::session data get "session.ad.last.attr.serialNumber"]
}
datagroup {
set ga_key [class lookup $username $static::ga_key_dg]
}
}
# increment the number of login attempts for the user
set prev_attempts [table incr -notouch -subtable $static::lockout_state_table $username]
table timeout -subtable $static::lockout_state_table $username $static::lockout_period
# verification result value:
# 0 = successful
# 1 = failed
# 2 = no key found
# 3 = invalid key length
# 4 = user locked out
# make sure that the user isn't locked out before calculating GA code
if { $prev_attempts <= $static::lockout_attempts } {
# check that a valid key was retrieved, then proceed
#Update the key length based on the organization requirements
if { [string length $ga_key] == 16 } {
# begin - Base32 decode to binary
# Base32 alphabet (see RFC 4648)
array set static::b32_alphabet {
A 0 B 1 C 2 D 3
E 4 F 5 G 6 H 7
I 8 J 9 K 10 L 11
M 12 N 13 O 14 P 15
Q 16 R 17 S 18 T 19
U 20 V 21 W 22 X 23
Y 24 Z 25 2 26 3 27
4 28 5 29 6 30 7 31
}
set ga_key [string toupper $ga_key]
set l [string length $ga_key]
set n 0
set j 0
set ga_key_bin ""
for { set i 0 } { $i < $l } { incr i } {
set n [expr $n << 5]
set n [expr $n + $static::b32_alphabet([string index $ga_key $i])]
set j [incr j 5]
if { $j >= 8 } {
set j [incr j -8]
append ga_key_bin [format %c [expr ($n & (0xFF << $j)) >> $j]]
}
}
# end - Base32 decode to binary
# begin - HMAC-SHA1 calculation of Google Auth token
set time [binary format W* [expr [clock seconds] / 30]]
set ipad ""
set opad ""
for { set j 0 } { $j < [string length $ga_key_bin] } { incr j } {
binary scan $ga_key_bin @${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]
}
while { $j < 64 } {
append ipad 6
append opad \\
incr j
}
binary scan [sha1 $opad[sha1 ${ipad}${time}]] H* token
# end - HMAC-SHA1 calculation of Google Auth hex token
# begin - extract code from Google Auth hex token
set offset [expr ([scan [string index $token end] %x] & 0x0F) << 1]
set ga_code [expr (0x[string range $token $offset [expr $offset + 7]] & 0x7FFFFFFF) % 1000000]
set ga_code [format %06d $ga_code]
# end - extract code from Google Auth hex token
if { $ga_code_attempt eq $ga_code } {
# code verification successful
set ga_result 0
} else {
# code verification failed
set ga_result 1
}
} elseif { [string length $ga_key] > 0 } {
# invalid key length, greater than 0, but not length not equal to 16 chars
set ga_result 3
} else {
# could not retrieve user's key
set ga_result 2
}
} else {
# user locked out due to too many failed attempts
set ga_result 4
}
# set code verification result in session variable
ACCESS::session data set session.custom.ga_result $ga_result
}
}
Related Content
- Two-Factor Authentication With Google Authenticator And APM
- Demystifying Time-based OTP
- Google Authenticator Token Verification iRule For APM
- APM Google Authenticator HTTP API
- Google Authenticator Verification iRule (TMOS v11.1+ optimized)
- UDF Lab
- Configuring MFA OTP
Note, Scan the Article photo for more BIG-IP Access Policy Manager (APM) info.
Published May 08, 2023
Version 1.0