Two Factor Authentication with Freeradius for Horizon View

At work we were evaluating different options to enable two factor authentication for VMware Horizon View. They were all more than we were interested in paying and none had the ability to integrate with the communication platforms that we were interested in utilizing for delivering the PIN used as the “second factor”. Given that, my director gave me the opportunity to innovate and develop something custom.

Before we get started, you should know that I will not be providing a complete solution for two factor authentication with freeradius. My intention in this post is to demonstrate a working example of freeradius issuing an Access-Challenge response to a VMware View authentication request to achieve two factor authentication. Further development will be necessary to provide a full “solution”. (Integrating the freeradius perl module with LDAP or some other central authentication mechanism as well as deliver PINs and validate them.) If you have any questions in regards to how I achieved this, feel free to ask in the comments.

I had been looking for a good reason to play with freeradius and I finally had one. After some research within VMware’s documentation I knew I needed to learn how to get freeradius to send an “Access-Challenge” response.

https://pubs.vmware.com/view-52/index.jsp?topic=%2Fcom.vmware.view.administration.doc%2FGUID-73027CC6-8EA6-4887-A1F7-B40BF664E353.html
“If the RADIUS server issues an access challenge, View Client displays a dialog box similar to the RSA SecurID prompt for the next token code.”

Unfortunately, getting freeradius to do this is not well documented, but here are a few links I used for my research:
http://wiki.freeradius.org/guide/multiOTP-HOWTO
https://lists.freeradius.org/pipermail/freeradius-users/2008-August/030680.html
http://motp.sourceforge.net/
http://lists.freeradius.org/pipermail/freeradius-users/2011-January/051466.html
https://www.howtoforge.com/how-to-use-freeradius-with-linotp-2-to-do-two-factor-authentication-with-one-time-passwords
http://lists.freeradius.org/pipermail/freeradius-users/2012-May/060929.html
http://techtitude.blogspot.com/2014/12/freeradius-pap-challenge-authentication.html
http://lists.freeradius.org/pipermail/freeradius-users/2009-February/035675.html
http://www.mail-archive.com/freeradius-users@lists.freeradius.org/msg47441.html
http://lists.freeradius.org/pipermail/freeradius-users/2013-February/065099.html

I also read a few chapters from this book to get a better understanding of the configuration and inner workings of freeradius.

After all my research I used the example.pl code that comes with the freeradius perl module and modified the authenticate function like so:

sub authenticate {
        # For debugging purposes only
#       &log_request_attributes;
        if ($RAD_REQUEST{'State'} eq "0x6368616c6c656e6765") {
                if($RAD_REQUEST{'User-Password'} eq "1234") {
                        $RAD_REPLY{'Reply-Message'} = "Access granted";
                        return RLM_MODULE_OK;
                } else {
                        $RAD_REPLY{'Reply-Message'} = "Denied access by rlm_perl function";
                        return RLM_MODULE_REJECT;
                }
        } else {
                if($RAD_REQUEST{'User-Name'} eq "testusernamehere" && $RAD_REQUEST{'User-Password'} eq "testpasswordhere") {
                        $RAD_REPLY{'State'} = "challenge";
                        $RAD_CHECK{'Response-Packet-Type'} = "Access-Challenge";
                        $RAD_REPLY{'Reply-Message'} = "Enter your PIN.";
                } else {
                        $RAD_REPLY{'Reply-Message'} = "Denied access by rlm_perl function";
                        return RLM_MODULE_REJECT;
                }
        }
}

The code above is extremely bare-bones and serves only as an example to use the perl module with freeradius to send an authenticator an Access-Challenge response to an authentication request. You will want to modify the “testusernamehere” and “testpasswordhere” strings to something more appropriate and optionally the “1234” test PIN. This code first authenticates a user by validating their username and password. If it is successful, an Access-Challenge response is sent to the authenticator and the “State” AVP (Attribute-Value Pair) is set to “challenge”. When the authenticator receives the Access-Challenge it prompts for a PIN. When the PIN is entered, the request is processed by the first block of code because the text value of the “State” AVP (challeng) now matches the hexadecimal string “0x6368616c6c656e6765” in the first if statement. This happens because in the previous request we set the State AVP to be equal to “challenge” which is the text equivalent to the hexadecimal string “0x6368616c6c656e6765”. The same User-Name is sent as used previously, but this time User-Password must match “1234”. Any other PIN will cause authentication to fail.

Here are screenshots of the Horizon View client authentication behavior using a freeradius server with this configuration.

two factor authentication vmware view first factor

two factor authentication vmware view second factor

3 thoughts on “Two Factor Authentication with Freeradius for Horizon View”

  1. What are other clients available to test Access-Challenge, i tried NTRadPing to do challenge, but nothing worked.

    freeRadius server log:
    (9) Received Access-Request Id 2 from 10.30.95.18:54789 to 10.30.95.25:1812 length 44
    (9) User-Name = “robo”
    (9) User-Password = “robo”
    (9) # Executing section authorize from file /usr/local/etc/raddb/sites-enabled/default
    (9) authorize {
    (9) policy filter_username {
    (9) if (&User-Name) {
    (9) if (&User-Name) -> TRUE
    (9) if (&User-Name) {
    (9) if (&User-Name =~ / /) {
    (9) if (&User-Name =~ / /) -> FALSE
    (9) if (&User-Name =~ /@[^@]*@/ ) {
    (9) if (&User-Name =~ /@[^@]*@/ ) -> FALSE
    (9) if (&User-Name =~ /\.\./ ) {
    (9) if (&User-Name =~ /\.\./ ) -> FALSE
    (9) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) {
    (9) if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) -> FALSE
    (9) if (&User-Name =~ /\.$/) {
    (9) if (&User-Name =~ /\.$/) -> FALSE
    (9) if (&User-Name =~ /@\./) {
    (9) if (&User-Name =~ /@\./) -> FALSE
    (9) } # if (&User-Name) = notfound
    (9) } # policy filter_username = notfound
    (9) [preprocess] = ok
    (9) [chap] = noop
    (9) [mschap] = noop
    (9) [digest] = noop
    (9) suffix: Checking for suffix after “@”
    (9) suffix: No ‘@’ in User-Name = “robo”, looking up realm NULL
    (9) suffix: No such realm “NULL”
    (9) [suffix] = noop
    (9) eap: No EAP-Message, not doing EAP
    (9) [eap] = noop
    (9) files: users: Matched entry DEFAULT at line 4
    (9) [files] = ok
    (9) [expiration] = noop
    (9) [logintime] = noop
    (9) pap: WARNING: No “known good” password found for the user. Not setting Auth-Type
    (9) pap: WARNING: Authentication will fail unless a “known good” password is available
    (9) [pap] = noop
    (9) } # authorize = ok
    (9) Found Auth-Type = Perl
    (9) # Executing group from file /usr/local/etc/raddb/sites-enabled/default
    (9) Auth-Type Perl {
    (9) perl: $RAD_REQUEST{‘User-Name’} = &request:User-Name -> ‘robo’
    (9) perl: $RAD_REQUEST{‘User-Password’} = &request:User-Password -> ‘robo’
    (9) perl: $RAD_REQUEST{‘NAS-IP-Address’} = &request:NAS-IP-Address -> ‘10.30.95.18’
    (9) perl: $RAD_REQUEST{‘Event-Timestamp’} = &request:Event-Timestamp -> ‘Jun 4 2016 06:48:43 PDT’
    (9) perl: $RAD_CHECK{‘Auth-Type’} = &control:Auth-Type -> ‘Perl’
    (9) perl: $RAD_CONFIG{‘Auth-Type’} = &control:Auth-Type -> ‘Perl’
    Use of uninitialized value $RAD_REQUEST{“State”} in string eq at /usr/local/etc/raddb/mods-config/perl/example.pl line 157.
    (9) perl: &request:User-Name = $RAD_REQUEST{‘User-Name’} -> ‘robo’
    (9) perl: &request:Event-Timestamp = $RAD_REQUEST{‘Event-Timestamp’} -> ‘Jun 4 2016 06:48:43 PDT’
    (9) perl: &request:User-Password = $RAD_REQUEST{‘User-Password’} -> ‘robo’
    (9) perl: &request:NAS-IP-Address = $RAD_REQUEST{‘NAS-IP-Address’} -> ‘10.30.95.18’
    (9) perl: &reply:Reply-Message = $RAD_REPLY{‘Reply-Message’} -> ‘Enter your PIN.’
    (9) perl: &reply:State = $RAD_REPLY{‘State’} -> ‘challenge’
    (9) perl: &control:Response-Packet-Type = $RAD_CHECK{‘Response-Packet-Type’} -> ‘Access-Challenge’
    (9) perl: &control:Auth-Type = $RAD_CHECK{‘Auth-Type’} -> ‘Perl’
    (9) [perl] = handled
    (9) } # Auth-Type Perl = handled
    (9) Using Post-Auth-Type Challenge
    (9) Post-Auth-Type sub-section not found. Ignoring.
    (9) # Executing group from file /usr/local/etc/raddb/sites-enabled/default
    (9) Sent Access-Challenge Id 2 from 10.30.95.25:1812 to 10.30.95.18:54789 length 0
    (9) Reply-Message = “Enter your PIN.”
    (9) State = 0x6368616c6c656e6765
    (9) Finished request
    Waking up in 4.9 seconds.
    (9) Cleaning up request packet ID 2 with timestamp +966
    Ready to process requests

    1. You can use radtest from the CLI to test it using a command like this “radtest username password 127.0.0.1 0 secret”. If Access-Challenge is properly configured, the response will look like this.
      Sending Access-Request of id 6 to 127.0.0.1 port 1812
      User-Name = "username"
      User-Password = "password"
      NAS-IP-Address = 192.168.50.50
      NAS-Port = 0
      Message-Authenticator = 0x00000000000000000000000000000000
      rad_recv: Access-Challenge packet from host 127.0.0.1 port 1812, id=6, length=85
      Reply-Message = "Enter your Pin"
      State = 0x6368616c6c656e6765

      I don’t know of any good radius testers. When I’ve looked in the past, very few of them appeared to support Access-Challenge. I’m not sure what application you’re configuring freeradius for, but some applications don’t allow you to customize the challenge prompt. For instance, regardless of what I respond from freeradius with in the Reply-Message, the VMware Horizon View password input says “Next Code:” next to it. Depending on which client you use, it may or may not say the message elsewhere in the UI.

Leave a Reply

Your email address will not be published. Required fields are marked *